0Pricing
PHP Academy · Lektion

Mocking und Stubbing mit Mockery

Einheiten mit flexiblen Test-Doubles isolieren

Mocking und Stubbing mit Mockery ist eine kostenlose PHP Academy-Lektion auf CoddyKit. Dies ist Lektion 2 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des PHP Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Warum eine dedizierte Mocking-Bibliothek

PHPUnit enthält zwar eine eigene API für Test-Doubles, aber Mockery bietet eine flüssigere, ausdrucksstärkere Syntax und Funktionen, die den Mocks von PHPUnit fehlen – partielle Mocks, Spies, flexible Argumentübereinstimmung und geordnete Erwartungen. Bei komplexem Code mit vielen Interaktionen ist Mockery oft deutlich besser lesbar. In dieser Lektion behandeln Sie das gesamte Spektrum der Test-Doubles und wann Sie welches einsetzen.

composer require --dev mockery/mockery

Stub, Mock oder Spy

Präzise Begriffe verhindern missverständliche Tests:

  • Stub – gibt vorgefertigte Werte zurück; Sie prüfen den Zustand (das Ergebnis).
  • Mock – enthält Erwartungen an Aufrufe; Sie prüfen das Verhalten (dass er korrekt aufgerufen wurde). Nicht erfüllte Erwartungen lassen den Test fehlschlagen.
  • Spy – zeichnet Aufrufe auf und ermöglicht es Ihnen, sie nachträglich zu prüfen.

Als Faustregel gilt: Verwenden Sie für Abfragen bevorzugt Stubs und für Befehle Mocks oder Spies.

Ein Stub mit allows()

Verwenden Sie Mockery::mock() und allows() (oder shouldReceive()->andReturn()), um vorgefertigte Rückgabewerte bereitzustellen, ohne zu prüfen, ob der Aufruf erfolgt ist. Hier liefert ein Gateway-Stub einen bekannten Wechselkurs, damit wir die Berechnung isoliert testen können.

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

Ein Mock mit expects()

Wenn die Interaktion selbst den Vertrag darstellt – zum Beispiel „eine E-Mail muss genau einmal gesendet werden“ –, verwenden Sie expects() oder shouldReceive()->once(). Mockery überprüft die Erwartung während Mockery::close(); ein nicht erfolgter Aufruf lässt den Test fehlschlagen.

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

Argument-Matcher

Mit Mockerys Matchern können Erwartungen so locker oder strikt sein wie nötig:

  • Mockery::any() – beliebiger Wert.
  • Mockery::type('string') / ein Klassenname – Typprüfung.
  • Mockery::on(fn($a) => ...) – benutzerdefinierte Prädikatsfunktion.
  • Mockery::capture($var) – Argument für spätere Prüfungen erfassen.
<?php
use Mockery;

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

Rückgabesequenzen und dynamische Rückgaben

Sie können über mehrere Aufrufe hinweg mehrere Rückgabewerte vorgeben oder den Rückgabewert mit andReturnUsing() aus den Argumenten berechnen. Damit lassen sich Wiederholungslogik, Paginierung oder zustandsbehaftete abhängige Komponenten modellieren.

<?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: Nachträglich prüfen

Ein Spy kehrt die Reihenfolge um: zuerst ausführen, dann prüfen. Mockery::spy() zeichnet Aufrufe auf; anschließend fragen Sie sie mit shouldHaveReceived() ab. Spies halten die Struktur Arrange-Act-Assert übersichtlich, wenn Sie die Vorbereitung nicht mit vorab festgelegten Erwartungen überladen möchten.

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

Partielle Mocks

Manchmal möchten Sie ein echtes Objekt verwenden, aber eine Methode überschreiben – einen partiellen Mock. Mockerys makePartial() ruft echte Methoden auf, außer für jene, für die Sie Erwartungen festgelegt haben. Verwenden Sie dies sparsam: Eine starke Abhängigkeit von partiellen Mocks deutet meist darauf hin, dass eine Klasse zu viele Aufgaben übernimmt.

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

Mocken Sie nichts, was Ihnen nicht gehört

Ein grundlegendes Testprinzip: Vermeiden Sie es, Klassen von Drittanbietern direkt zu mocken. Ihre APIs können sich ändern, und Ihr Mock entfernt sich unbemerkt von der Realität. Kapseln Sie sie stattdessen hinter Ihrer eigenen Schnittstelle und mocken Sie diese. Der Mock überprüft dann Ihren Vertrag, während ein Integrationstest für den Adapter die tatsächliche Anbindung überprüft.

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

Immer schließen und Aufrufanzahlen prüfen

Zwei wichtige Maßnahmen für den Betrieb:

  • Rufen Sie Mockery::close() in tearDown() auf (oder verwenden Sie das Trait MockeryPHPUnitIntegration), damit Erwartungen tatsächlich überprüft und globale Zustände bereinigt werden.
  • Verwenden Sie explizite Anzahlen (once(), times(n), never()) – vage Mocks lassen Fehler durch.
<?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 ...
    }
}

Geordnete Erwartungen

Gelegentlich gehört die Reihenfolge der Aufrufe zum Vertrag – Sie müssen beginTransaction() vor commit() aufrufen. Mockerys ordered() erzwingt diese Reihenfolge und lässt den Test fehlschlagen, wenn Aufrufe in der falschen Reihenfolge eintreffen. Verwenden Sie dies nur, wenn die Reihenfolge wirklich wichtig ist; eine zu genaue Festlegung macht Tests instabil.

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

Schnelltest

Stub oder Mock – welches wofür?

Zusammenfassung

Sie beherrschen Mockerys Test-Doubles:

  • Stubs (allows) für Abfragen, Mocks (expects) für Befehle und Spies für nachträgliche Prüfungen.
  • Argument-Matcher (type, on, capture) passen den Grad der Strenge an.
  • Rückgabesequenzen und andReturnUsing modellieren zustandsbehaftete oder dynamische abhängige Komponenten.
  • Partielle Mocks überschreiben einzelne Methoden; verwenden Sie sie sparsam.
  • Mocken Sie nichts, was Ihnen nicht gehört – kapseln Sie Drittanbieter hinter Ihrer Schnittstelle.
  • Rufen Sie immer Mockery::close() auf und prüfen Sie explizite Aufrufanzahlen.

Als Nächstes: Integrations- und Funktionstests.

Häufig gestellte Fragen

Ist die Lektion „Mocking und Stubbing mit Mockery“ kostenlos?

Ja — der vollständige Text von „Mocking und Stubbing mit Mockery“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des PHP Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Mocking und Stubbing mit Mockery“?

Einheiten mit flexiblen Test-Doubles isolieren Du übst PHP Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um PHP Academy zu starten?

Keine Vorkenntnisse erforderlich. PHP Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 4.

Wie lange dauert die Lektion „Mocking und Stubbing mit Mockery“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser PHP Academy-Lektion Code schreiben und ausführen?

Ja. Jede PHP Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Der Test-Driven-Development-Workflow
  2. Mocking und Stubbing mit Mockery
  3. Integrations- und Funktionstests
  4. Mutation Testing mit Infection
← Zurück zu PHP Academy