PHP Academy · Lektion

Mocking og stubbing med Mockery

Isolér enheder med fleksible test doubles.

Lektion 2 af 413 trin

Mocking og stubbing med Mockery er en gratis PHP Academy-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i PHP Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. PHP Academy-kurset indeholder 4 lektioner i alt.

Hvorfor et dedikeret bibliotek til mock-objekter

PHPUnit leveres med sin egen API til testsubstitutter, men Mockery tilbyder en mere flydende og udtryksfuld syntaks samt funktioner, som PHPUnit-mocks mangler — delvise mocks, spies, fleksibel argumentmatching og forventninger med rækkefølge. Til kompleks kode med mange samarbejder er Mockery ofte langt lettere at læse. Denne lektion dækker hele spektret af testsubstitutter og hvornår du skal bruge hver type.

composer require --dev mockery/mockery

Stub kontra mock kontra spy

Præcist ordforråd forebygger forvirrede tests:

  • Stub — returnerer foruddefinerede værdier; du kontrollerer tilstanden (resultatet).
  • Mock — har forventninger til kald; du kontrollerer adfærden (at det blev kaldt korrekt). Uopfyldte forventninger får testen til at fejle.
  • Spy — registrerer kald og lader dig kontrollere dem bagefter.

Tommelfingerregel: Foretræk stubs til forespørgsler og mocks/spies til kommandoer.

En stub med allows()

Brug Mockery::mock() og allows() (eller shouldReceive()->andReturn()) til at levere foruddefinerede returværdier uden at kontrollere, om kaldet blev foretaget. Her leverer en gateway-stub en kendt vekselkurs, så vi kan teste beregningen isoleret.

<?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();
    }
}

En mock med expects()

Når selve interaktionen er kontrakten — f.eks. "en e-mail skal sendes præcis én gang" — skal du bruge expects() eller shouldReceive()->once(). Mockery kontrollerer forventningen under Mockery::close(); hvis det forventede kald mangler, fejler testen.

<?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

Argumentmatchere

Mockerys matchere lader forventninger være så løse eller strenge, som det er nødvendigt:

  • Mockery::any() — enhver værdi.
  • Mockery::type('string') / et klassenavn — typekontrol.
  • Mockery::on(fn($a) => ...) — brugerdefineret prædikat.
  • Mockery::capture($var) — gem argumentet til senere kontroller.
<?php
use Mockery;

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

Retursekvenser og dynamiske returværdier

Du kan angive flere returværdier på tværs af kald eller beregne returværdien ud fra argumenterne med andReturnUsing(). Det modellerer logik til genforsøg, sideinddeling eller samarbejdspartnere med tilstand.

<?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: Kontrollér bagefter

En spy vender rækkefølgen om: handl først, og kontrollér derefter. Mockery::spy() registrerer kald; bagefter undersøger du dem med shouldHaveReceived(). Spies holder Arrange-Act-Assert-strukturen ren, når du ikke ønsker, at forudindstillede forventninger skal fylde i opsætningen.

<?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();

Delvise mocks

Nogle gange vil du have et rigtigt objekt, men med én metode tilsidesat — en delvis mock. Mockerys makePartial() kalder de rigtige metoder, undtagen dem du har angivet forventninger til. Brug det sparsomt: Stor afhængighed af delvise mocks tyder som regel på en klasse, der gør for meget.

<?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();

Mock ikke det, du ikke ejer

Et grundprincip i testning: Undgå at mocke tredjepartsklasser direkte. Deres API'er kan ændre sig, og din mock kommer ubemærket på afstand af virkeligheden. Omslut dem i stedet bag din egen grænseflade, og mock den. Mocken kontrollerer derefter din kontrakt, mens en integrationstest af adapteren kontrollerer den virkelige kobling.

<?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.

Luk altid, og kontrollér antal

To obligatoriske handlinger:

  • Kald Mockery::close() i tearDown() (eller brug traitet MockeryPHPUnitIntegration), så forventningerne faktisk kontrolleres, og globale tilstande ryddes op.
  • Brug eksplicitte antal (once(), times(n), never()) — vage mocks slipper fejl igennem.
<?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 ...
    }
}

Forventninger med rækkefølge

Af og til er rækkefølgen af kald en del af kontrakten — du skal kalde beginTransaction() før commit(). Mockerys ordered() håndhæver rækkefølgen og får testen til at fejle, hvis kald kommer i forkert rækkefølge. Brug det kun, når rækkefølgen reelt betyder noget; for detaljerede rækkefølgekrav gør tests skrøbelige.

<?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()

Hurtigt tjek

Stub eller mock — hvad bruger du til hvad?

Opsummering

Du har fået styr på Mockerys testsubstitutter:

  • Stubs (allows) til forespørgsler; mocks (expects) til kommandoer; spies til kontroller bagefter.
  • Argumentmatchere (type, on, capture) justerer strengheden.
  • Retursekvenser og andReturnUsing modellerer samarbejdspartnere med tilstand og dynamiske samarbejdspartnere.
  • Delvise mocks tilsidesætter enkelte metoder; brug dem sparsomt.
  • Mock ikke det, du ikke ejer — omslut tredjepartskode bag din grænseflade.
  • Kald altid Mockery::close(), og kontrollér eksplicitte antal kald.

Næste emne: integrations- og funktionel testning.

Gratis at komme i gang

Lær PHP med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
49
Lektioner
195

Ofte stillede spørgsmål

Er lektionen “Mocking og stubbing med Mockery” gratis?

Ja — hele teksten til “Mocking og stubbing med Mockery” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af PHP Academy-kurset, skal du opgradere til CoddyKit PRO. PHP Academy-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Mocking og stubbing med Mockery”?

Isolér enheder med fleksible test doubles. Du øver dig i PHP Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på PHP Academy?

Der kræves ingen tidligere erfaring. PHP Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.

Hvor lang tid tager lektionen “Mocking og stubbing med Mockery”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne PHP Academy-lektion?

Ja. Alle PHP Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Arbejdsgangen i testdrevet udvikling
  2. Mocking og stubbing med Mockery
  3. Integrationstest og funktionel test
  4. Mutationstest med Infection
← Tilbage til PHP Academy