0Pricing
PHP Academy · Lekcja

Mockowanie i stubowanie za pomocą Mockery

Izoluj jednostki za pomocą elastycznych atrap testowych

Mockowanie i stubowanie za pomocą Mockery to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.

Dlaczego warto użyć dedykowanej biblioteki do mockowania

PHPUnit dostarcza własne API dublerów testowych, ale Mockery oferuje bardziej płynną, wyrazistą składnię oraz możliwości, których brakuje mockom PHPUnit — mocki częściowe, szpiegi, elastyczne dopasowywanie argumentów i oczekiwania dotyczące kolejności. W przypadku złożonego kodu opartego na współpracy obiektów Mockery często jest znacznie czytelniejsze. W tej lekcji omówiono pełne spektrum dublerów oraz sytuacje, w których należy używać każdego z nich.

composer require --dev mockery/mockery

Stub, mock czy spy

Precyzyjne słownictwo zapobiega niejasnym testom:

  • Stub — zwraca z góry ustalone wartości; asercje dotyczą stanu (wyniku).
  • Mock — zawiera oczekiwania dotyczące wywołań; asercje dotyczą zachowania (tego, czy został poprawnie wywołany). Niespełnione oczekiwania powodują niepowodzenie testu.
  • Spy — rejestruje wywołania i pozwala wykonywać asercje po fakcie.

Praktyczna zasada: stubów należy używać przede wszystkim do zapytań, a mocków i szpiegów do poleceń.

Stub z allows()

Należy użyć Mockery::mock() i allows() (lub shouldReceive()->andReturn()), aby dostarczyć z góry ustalone wartości zwracane bez sprawdzania, czy wywołanie rzeczywiście nastąpiło. W tym przykładzie stub bramki dostarcza znany kurs wymiany, dzięki czemu można przetestować obliczenia w izolacji.

<?php
use Mockery;
use PHPUnit\Framework\TestCase;

final class ConverterTest extends TestCase
{
    public function test_converts_using_rate(): void
    {
        $rates = Mockery::mock(RateGateway::class);
        $rates->allows()->rateFor('USD', 'EUR')->andReturn(0.9);

        $sut = new Converter($rates);
        self::assertSame(90.0, $sut->convert(100.0, 'USD', 'EUR'));

        Mockery::close();
    }
}

Mock z expects()

Gdy sama interakcja stanowi kontrakt — na przykład „wiadomość e-mail musi zostać wysłana dokładnie raz” — należy użyć expects() lub shouldReceive()->once(). Mockery weryfikuje oczekiwanie podczas wywołania Mockery::close(); brak wymaganego wywołania powoduje niepowodzenie testu.

<?php
use Mockery;

$mailer = Mockery::mock(Mailer::class);
$mailer->expects()
       ->send(Mockery::type(Message::class))
       ->once();

$service = new SignupService($mailer);
$service->register('ada@example.com');

Mockery::close(); // fails here if send() was never called

Dopasowywanie argumentów

Dopasowania Mockery pozwalają ustawić oczekiwania tak luźno lub rygorystycznie, jak to potrzebne:

  • Mockery::any() — dowolna wartość.
  • Mockery::type('string') / nazwa klasy — sprawdzenie typu.
  • Mockery::on(fn($a) => ...) — własny predykat.
  • Mockery::capture($var) — przechwycenie argumentu do późniejszych asercji.
<?php
use Mockery;

$repo = Mockery::mock(UserRepo::class);
$repo->expects()
     ->save(Mockery::on(fn(User $u) => $u->isActive()))
     ->once()
     ->andReturnTrue();

Sekwencje wartości zwracanych i zwroty dynamiczne

Można zaprogramować wiele wartości zwracanych dla kolejnych wywołań albo obliczać wartość zwracaną na podstawie argumentów za pomocą andReturnUsing(). Pozwala to modelować logikę ponawiania prób, paginację lub współpracujące obiekty ze stanem.

<?php
use Mockery;

$api = Mockery::mock(HttpClient::class);
// First call throws, second succeeds (retry test):
$api->shouldReceive('get')
    ->twice()
    ->andThrow(new \RuntimeException('timeout'))
    ->andReturn('{"ok":true}');

// Or derive the return from the input:
$api->shouldReceive('echo')
    ->andReturnUsing(fn(string $in) => strtoupper($in));

Spies: asercje po fakcie

Spy odwraca kolejność: najpierw następuje działanie, a potem asercja. Mockery::spy() rejestruje wywołania, które następnie można sprawdzić za pomocą shouldHaveReceived(). Spies pomagają zachować przejrzystą strukturę Arrange-Act-Assert, gdy wstępnie ustawione oczekiwania nie powinny zaśmiecać konfiguracji.

<?php
use Mockery;

$logger = Mockery::spy(Logger::class);

$service = new PaymentService($logger);
$service->charge(500);

// Assertions happen AFTER the action:
$logger->shouldHaveReceived('info')
       ->with(Mockery::pattern('/charged 500/'))
       ->once();

Mockery::close();

Mocki częściowe

Czasami potrzebny jest rzeczywisty obiekt z jedną zastąpioną metodą — to mock częściowy. makePartial() z Mockery przekazuje wywołania do rzeczywistych metod, z wyjątkiem tych, dla których ustawiono oczekiwania. Należy korzystać z tego oszczędnie: częste używanie mocków częściowych zwykle sygnalizuje, że klasa robi zbyt wiele.

<?php
use Mockery;

$report = Mockery::mock(Report::class)->makePartial();
// Only stub the slow/external method; the rest runs for real
$report->shouldReceive('fetchRawData')->andReturn(['a', 'b', 'c']);

// real summarize() runs, using the stubbed data
$summary = $report->summarize();

Nie mockuj kodu, którego nie kontrolujesz

Podstawowa zasada testowania: należy unikać bezpośredniego mockowania klas firm trzecich. Ich API może się zmieniać, a mock po cichu rozjeżdżać się z rzeczywistością. Zamiast tego należy opakować je za pomocą własnego interfejsu i mockować właśnie ten interfejs. Mock weryfikuje wtedy własny kontrakt, a test integracyjny adaptera sprawdza rzeczywistą integrację.

<?php
interface PaymentGateway {            // you own this
    public function charge(int $cents, string $token): string;
}

final class StripeGateway implements PaymentGateway {
    public function __construct(private \Stripe\StripeClient $client) {}
    public function charge(int $cents, string $token): string {
        return $this->client->paymentIntents->create([/* ... */])->id;
    }
}
// Tests mock PaymentGateway, never \Stripe\StripeClient directly.

Zawsze wywołuj close i weryfikuj liczbę wywołań

Dwa obowiązkowe działania:

  • Należy wywoływać Mockery::close() w tearDown() (lub użyć cechy MockeryPHPUnitIntegration), aby oczekiwania były rzeczywiście weryfikowane, a globalny stan czyszczony.
  • Należy używać jawnych liczników (once(), times(n), never()) — nieprecyzyjne mocki przepuszczają błędy.
<?php
use PHPUnit\Framework\TestCase;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;

final class OrderServiceTest extends TestCase
{
    use MockeryPHPUnitIntegration; // auto-calls Mockery::close()

    public function test_does_not_refund_paid_orders(): void
    {
        $gw = \Mockery::mock(PaymentGateway::class);
        $gw->shouldReceive('refund')->never();
        // ... exercise SUT ...
    }
}

Oczekiwania dotyczące kolejności

Czasami kolejność wywołań jest częścią kontraktu — przed wywołaniem commit() trzeba wywołać beginTransaction(). ordered() z Mockery wymusza kolejność, powodując niepowodzenie testu, jeśli wywołania nadejdą w innej kolejności. Należy używać tego tylko wtedy, gdy kolejność rzeczywiście ma znaczenie; nadmierne określanie kolejności prowadzi do kruchych testów.

<?php
use Mockery;

$tx = Mockery::mock(Transaction::class);
$tx->shouldReceive('begin')->once()->ordered();
$tx->shouldReceive('commit')->once()->ordered();

$service = new TransferService($tx);
$service->run();

Mockery::close(); // fails if commit() happened before begin()

Szybkie sprawdzenie

Stub czy mock — którego użyć i kiedy?

Podsumowanie

Opanowano duble Mockery:

  • Stuby (allows) służą do zapytań, mocki (expects) do poleceń, a spies do asercji wykonywanych po fakcie.
  • Dopasowania argumentów (type, on, capture) pozwalają dostosować rygorystyczność.
  • Sekwencje wartości zwracanych i andReturnUsing modelują współpracujące obiekty ze stanem lub dynamicznym zachowaniem.
  • Mocki częściowe zastępują pojedyncze metody; należy używać ich oszczędnie.
  • Nie należy mockować kodu, którego się nie kontroluje — firmy trzecie trzeba opakować za pomocą własnego interfejsu.
  • Zawsze należy wywoływać Mockery::close() i sprawdzać jawną liczbę wywołań.

Dalej: testowanie integracyjne i funkcjonalne.

Często zadawane pytania

Czy lekcja „Mockowanie i stubowanie za pomocą Mockery” jest bezpłatna?

Tak — pełny tekst „Mockowanie i stubowanie za pomocą Mockery” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Mockowanie i stubowanie za pomocą Mockery”?

Izoluj jednostki za pomocą elastycznych atrap testowych Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć PHP Academy?

Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Mockowanie i stubowanie za pomocą Mockery”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?

Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Przepływ pracy wytwarzania sterowanego testami
  2. Mockowanie i stubowanie za pomocą Mockery
  3. Testy integracyjne i funkcjonalne
  4. Testy mutacyjne za pomocą Infection
← Powrót do PHP Academy