Dummy, Stub, Fake, Spy und Mock ohne Framework-Bindung verstanden
Wer in Unit-Tests wahllos alles mockt, was mit einer Klasse zusammenhängt, baut Tests, die bei jedem Refactoring brechen, ohne dass sich am Verhalten der Anwendung etwas geändert hat. Die Taxonomie nach Gerard Meszaros unterscheidet Dummy, Stub, Fake, Spy und Mock als fünf klar abgegrenzte Rollen mit unterschiedlichem Zweck, unterschiedlicher Prüflogik und unterschiedlichem Kopplungsgrad an Implementierungsdetails. Dieser Beitrag zeigt die Theorie hinter jedem Test Double anhand handgeschriebener PHP-Implementierungen, unabhängig von einem bestimmten Test-Framework.
Inhaltsverzeichnis
- 1. Warum die Wahl des richtigen Test Double über Testqualität entscheidet
- 2. Die Taxonomie: Dummy, Stub, Fake, Spy, Mock im Detail
- 3. Stubs: vorgegebene Antworten für Zustandsprüfung
- 4. Mocks: Verhaltensverifikation statt Zustandsprüfung
- 5. Fakes: funktionierende Implementierungen für Tests
- 6. Spies: Interaktionen aufzeichnen ohne Erwartungen im Voraus
- 7. Overmocking: wenn Mocks Refactoring verhindern statt zu unterstützen
- 8. London School vs. Chicago/Detroit School of TDD
- 9. Test Doubles im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum die Wahl des richtigen Test Double über Testqualität entscheidet
Ein Test Double ersetzt eine echte Abhängigkeit in einem Unit-Test durch ein kontrolliertes Double, damit sich der Test ausschließlich auf die zu prüfende Einheit konzentriert. Die Wahl, welche Art von Double an welcher Stelle eingesetzt wird, entscheidet aber darüber, ob ein Test tatsächlich Vertrauen in die Korrektheit des Codes schafft oder nur eine aufwendige Attrappe der Implementierung ist. Wird beispielsweise ein einfacher Werteanbieter mit einem vollwertigen Mock samt Erwartungsprüfung ausgestattet, obwohl ein simpler Stub völlig ausreichen würde, koppelt der Test sich unnötig eng an interne Aufrufreihenfolgen, die mit dem eigentlich zu prüfenden Verhalten nichts zu tun haben.
Genau diese Fehlentscheidung ist der Grund, warum Teams nach einem harmlosen Refactoring plötzlich Dutzende fehlschlagender Tests vor sich haben, obwohl sich am externen Verhalten der Anwendung nichts geändert hat. Ein Test, der bei jeder internen Umstrukturierung bricht, verliert seinen Wert als Sicherheitsnetz und wird stattdessen zur Bremse, die Entwickler dazu verleitet, Tests einfach anzupassen, ohne die eigentliche Fehlermeldung ernst zu nehmen. Die richtige Mocking-Strategie, die für jede Abhängigkeit das passende Test Double aus der Taxonomie von Dummy, Stub, Fake, Spy und Mock wählt, ist deshalb keine akademische Feinheit, sondern eine Grundvoraussetzung für Tests, die tatsächlich beim Refactoring helfen statt es zu verhindern.
2. Die Taxonomie: Dummy, Stub, Fake, Spy, Mock im Detail
Der Softwareentwickler Gerard Meszaros hat in seinem Standardwerk xUnit Test Patterns eine Taxonomie von fünf Test Doubles etabliert, die bis heute die präziseste Grundlage für Diskussionen über Mocking-Strategien liefert. Ein Dummy ist ein Objekt, das lediglich als Platzhalter übergeben wird, um eine Methodensignatur zu erfüllen, im Testverlauf aber nie tatsächlich verwendet wird, etwa ein Logger-Objekt, das an einen Konstruktor übergeben werden muss, dessen Aufrufe im konkreten Test jedoch irrelevant sind. Ein Stub liefert vorgegebene, feste Antworten auf Methodenaufrufe und versetzt den Test dadurch in einen bestimmten Zustand, ohne die eigentliche Logik der echten Abhängigkeit auszuführen.
Ein Fake ist eine funktionierende, aber vereinfachte Implementierung derselben Schnittstelle wie die echte Abhängigkeit, etwa ein In-Memory-Repository anstelle einer Datenbankanbindung. Ein Spy zeichnet auf, wie er aufgerufen wurde, damit der Test diese Aufrufe im Nachhinein prüfen kann, ohne dass vorab Erwartungen definiert werden müssen. Ein Mock schließlich kennt seine Erwartungen bereits vor der Ausführung und lässt den Test fehlschlagen, sobald eine erwartete Interaktion nicht wie spezifiziert stattfindet. Der zentrale Unterschied zwischen den fünf Kategorien liegt darin, ob ein Test Double den Zustand nach der Ausführung prüft, wie bei Stub und Fake, oder das Verhalten während der Ausführung verifiziert, wie bei Spy und Mock, während der Dummy als einziger Vertreter überhaupt keine Prüffunktion übernimmt.
3. Stubs: vorgegebene Antworten für Zustandsprüfung
Ein Stub ist die einfachste Form eines aktiven Test Double und beantwortet Methodenaufrufe mit fest einprogrammierten, vorab festgelegten Werten. Der Zweck eines Stubs ist ausschließlich, die zu testende Einheit in einen bestimmten, reproduzierbaren Ausgangszustand zu versetzen, damit der eigentliche Testfall unter kontrollierten Bedingungen ablaufen kann. Ein Stub führt keine eigene Logik aus und trifft keine Aussage darüber, wie oft oder mit welchen Argumenten er aufgerufen wurde, er liefert lediglich bei jedem Aufruf denselben vorbereiteten Rückgabewert.
Diese Beschränkung auf reine Zustandsprüfung ist gleichzeitig die größte Stärke eines Stubs: Der Test bleibt robust gegenüber internen Refactorings der zu testenden Einheit, solange sich das nach außen sichtbare Verhalten nicht ändert. Ein typisches Beispiel ist ein PriceProviderInterface, das in einer echten Implementierung Preise aus einer externen API oder Datenbank lädt. In einem Unit-Test für eine Rabattberechnung interessiert nicht, woher der Preis kommt, sondern nur, dass ein fester, bekannter Preis zur Verfügung steht, gegen den die Berechnung geprüft werden kann. Ein handgeschriebener StubPriceProvider, der immer denselben Wert zurückgibt, ersetzt hier jede komplexere Mocking-Konstruktion vollständig.
<?php
declare(strict_types=1);
interface PriceProviderInterface
{
public function getPrice(string $sku): float;
}
// Stub: returns a fixed, canned value, no real lookup logic involved
final class StubPriceProvider implements PriceProviderInterface
{
public function __construct(private readonly float $fixedPrice = 19.99)
{
}
public function getPrice(string $sku): float
{
return $this->fixedPrice;
}
}
final class DiscountCalculator
{
public function __construct(private readonly PriceProviderInterface $priceProvider)
{
}
public function calculateDiscountedPrice(string $sku, float $discountPercent): float
{
$price = $this->priceProvider->getPrice($sku);
return round($price * (1 - $discountPercent / 100), 2);
}
}
// Test-like usage: the stub puts the calculator into a known, fixed state
$stub = new StubPriceProvider(100.00);
$calculator = new DiscountCalculator($stub);
$result = $calculator->calculateDiscountedPrice('SKU-1', 10.0);
// $result === 90.0, verified against a known state, not against provider calls
4. Mocks: Verhaltensverifikation statt Zustandsprüfung
Ein Mock unterscheidet sich fundamental von einem Stub, weil er nicht nur Antworten liefert, sondern selbst zur Prüfinstanz des Tests wird. Ein Mock kennt bereits vor der Ausführung, welche Methodenaufrufe mit welchen Argumenten und in welcher Häufigkeit erwartet werden, und der Test schlägt fehl, sobald diese Erwartung nicht exakt erfüllt wird. Diese Form der Prüfung nennt man Verhaltensverifikation, im Gegensatz zur Zustandsprüfung eines Stubs oder Fakes: Statt einen Rückgabewert oder einen veränderten Zustand nach der Ausführung zu untersuchen, prüft ein Mock, ob eine bestimmte Interaktion überhaupt stattgefunden hat.
Ein klassisches Beispiel ist die Prüfung, ob eine Benachrichtigung tatsächlich versendet wurde, wenn die eigentliche Geschäftslogik keinen direkt beobachtbaren Rückgabewert liefert. Ein handgeschriebener Mock für ein NotifierInterface zeichnet den Aufruf von notify() mit den übergebenen Argumenten auf und stellt in einer expliziten verify()-Methode sicher, dass der Aufruf genau einmal mit exakt den erwarteten Werten stattgefunden hat. Bleibt der Aufruf aus oder weichen die Argumente ab, schlägt die Verifikation fehl. Diese Art von Test ist wertvoll, wenn das beobachtbare Verhalten der Einheit tatsächlich in einem Seiteneffekt besteht, etwa dem Versand einer Benachrichtigung, und keine andere Möglichkeit existiert, diesen Effekt über einen Rückgabewert zu prüfen. In PHPUnit übernimmt createMock() diese Aufgabe komfortabler, folgt intern aber demselben Prinzip.
<?php
declare(strict_types=1);
interface NotifierInterface
{
public function notify(string $recipient, string $message): void;
}
// Hand-written mock: expectations are declared before execution
final class MockNotifier implements NotifierInterface
{
private int $callCount = 0;
/** @var array{recipient: string, message: string}|null */
private ?array $lastCall = null;
public function notify(string $recipient, string $message): void
{
$this->callCount++;
$this->lastCall = ['recipient' => $recipient, 'message' => $message];
}
/**
* @throws RuntimeException When the expected interaction did not occur.
*/
public function verifyCalledOnceWith(string $expectedRecipient, string $expectedMessage): void
{
if ($this->callCount !== 1) {
throw new RuntimeException(sprintf('Expected exactly 1 call, got %d', $this->callCount));
}
if ($this->lastCall['recipient'] !== $expectedRecipient || $this->lastCall['message'] !== $expectedMessage) {
throw new RuntimeException('notify() was called with unexpected arguments');
}
}
}
final class OrderShippedHandler
{
public function __construct(private readonly NotifierInterface $notifier)
{
}
public function handle(string $customerEmail, string $orderNumber): void
{
$this->notifier->notify($customerEmail, "Order {$orderNumber} has shipped");
}
}
// Test-like usage: expectation is verified only after the call happened
$mock = new MockNotifier();
$handler = new OrderShippedHandler($mock);
$handler->handle('customer@example.com', 'ORD-42');
$mock->verifyCalledOnceWith('customer@example.com', 'Order ORD-42 has shipped');
5. Fakes: funktionierende Implementierungen für Tests
Ein Fake unterscheidet sich von Stub und Mock dadurch, dass er tatsächlich funktionierende Logik enthält, statt nur vorgegebene Werte zurückzugeben oder Aufrufe zu protokollieren. Ein Fake implementiert dieselbe Schnittstelle wie die echte Abhängigkeit vollständig, aber mit einer vereinfachten internen Umsetzung, die für Produktionszwecke ungeeignet, für Testzwecke aber vollkommen ausreichend ist. Das häufigste Beispiel ist ein In-Memory-Repository, das Datensätze in einem einfachen Array statt in einer Datenbank hält, dabei aber echte Such-, Filter- und Speicherlogik implementiert.
Ein InMemoryOrderRepository, das die OrderRepositoryInterface vollständig implementiert, verhält sich für den Aufrufer identisch zu einer echten, datenbankgestützten Implementierung: Ein gespeicherter Datensatz kann anschließend über dieselbe Schnittstelle wieder abgefragt werden, Filterbedingungen werden tatsächlich ausgewertet, und Zustandsänderungen zwischen mehreren Aufrufen bleiben erhalten. Diese Eigenschaft macht Fakes besonders wertvoll für Integrationstests mehrerer zusammenarbeitender Komponenten, in denen Stubs mit ihren starren Rückgabewerten und Mocks mit ihren strengen Erwartungen schnell an ihre Grenzen stoßen, weil realistisches, zustandsbehaftetes Verhalten über mehrere Methodenaufrufe hinweg gebraucht wird.
<?php
declare(strict_types=1);
interface OrderRepositoryInterface
{
public function save(Order $order): void;
public function findById(int $id): ?Order;
/**
* @return Order[]
*/
public function findByCustomerId(int $customerId): array;
}
final class Order
{
public function __construct(
public readonly int $id,
public readonly int $customerId,
public readonly float $total,
) {
}
}
// Fake: a real, working implementation, simplified for test purposes
final class InMemoryOrderRepository implements OrderRepositoryInterface
{
/** @var array<int, Order> */
private array $orders = [];
public function save(Order $order): void
{
$this->orders[$order->id] = $order;
}
public function findById(int $id): ?Order
{
return $this->orders[$id] ?? null;
}
public function findByCustomerId(int $customerId): array
{
return array_values(
array_filter(
$this->orders,
static fn (Order $order): bool => $order->customerId === $customerId
)
);
}
}
// Test-like usage: real save/find logic runs, no database required
$repository = new InMemoryOrderRepository();
$repository->save(new Order(1, 42, 99.90));
$repository->save(new Order(2, 42, 15.00));
$customerOrders = $repository->findByCustomerId(42);
// count($customerOrders) === 2, filtering actually happened in the fake
6. Spies: Interaktionen aufzeichnen ohne Erwartungen im Voraus
Ein Spy zeichnet jede Interaktion auf, die während der Testausführung stattfindet, ohne dass vorab irgendeine Erwartung formuliert werden muss. Der Test entscheidet erst nach der Ausführung der zu prüfenden Einheit, welche der aufgezeichneten Aufrufe tatsächlich relevant sind, und formuliert die Assertions gegen diese aufgezeichneten Daten. Ein SpyLogger etwa speichert jeden aufgerufenen Log-Level samt Nachricht in einem internen Array, ohne dass der Test im Voraus festlegen muss, wie oft geloggt wird oder mit welchem exakten Text.
Der entscheidende Unterschied zu einem Mock liegt im Zeitpunkt der Prüfung: Ein Mock definiert seine Erwartungen vor der Ausführung und lässt den Test während oder unmittelbar nach einem abweichenden Aufruf fehlschlagen, ein Spy dagegen sammelt zunächst passiv alle Informationen und überlässt die eigentliche Prüfung vollständig dem Testcode nach Abschluss der Ausführung. Dieses Verzögern der Assertion macht Spies flexibler, weil ein einziger Testlauf mehrere unterschiedliche Aspekte derselben Aufzeichnung prüfen kann, etwa sowohl die Anzahl der Aufrufe als auch die Reihenfolge, ohne dass dafür mehrere unterschiedlich konfigurierte Mock-Objekte notwendig wären.
<?php
declare(strict_types=1);
interface LoggerInterface
{
public function log(string $level, string $message): void;
}
// Spy: records every call, no expectations are set beforehand
final class SpyLogger implements LoggerInterface
{
/** @var array<int, array{level: string, message: string}> */
private array $recordedCalls = [];
public function log(string $level, string $message): void
{
$this->recordedCalls[] = ['level' => $level, 'message' => $message];
}
/**
* @return array<int, array{level: string, message: string}>
*/
public function getRecordedCalls(): array
{
return $this->recordedCalls;
}
public function hasLoggedLevel(string $level): bool
{
foreach ($this->recordedCalls as $call) {
if ($call['level'] === $level) {
return true;
}
}
return false;
}
}
final class ImportJob
{
public function __construct(private readonly LoggerInterface $logger)
{
}
/**
* @param array<int, array<string, mixed>> $rows
*/
public function run(array $rows): void
{
$this->logger->log('info', 'Import started');
foreach ($rows as $row) {
if ($row === []) {
$this->logger->log('warning', 'Skipped empty row');
continue;
}
}
$this->logger->log('info', 'Import finished');
}
}
// Test-like usage: assertions are formed after execution, against recorded data
$spy = new SpyLogger();
$job = new ImportJob($spy);
$job->run([['sku' => 'A1'], [], ['sku' => 'B2']]);
$hasWarning = $spy->hasLoggedLevel('warning'); // true
$totalCalls = count($spy->getRecordedCalls()); // 3
// No expectations were set in advance, only recorded and checked afterward
7. Overmocking: wenn Mocks Refactoring verhindern statt zu unterstützen
Overmocking beschreibt die Praxis, jede einzelne Abhängigkeit einer Klasse durch ein Mock zu ersetzen und dabei nicht nur das nach außen sichtbare Verhalten, sondern auch interne Implementierungsdetails wie die exakte Aufrufreihenfolge mehrerer Hilfsmethoden zu verifizieren. Ein derart überspezifizierter Test prüft im Ergebnis nicht mehr, ob die Klasse ihre Aufgabe korrekt erfüllt, sondern ob sie intern exakt so implementiert ist wie zum Zeitpunkt der Testerstellung. Jede spätere, verhaltensneutrale Umstrukturierung des internen Ablaufs lässt einen solchen Test fehlschlagen, obwohl aus Sicht der Aufrufer nichts kaputt gegangen ist.
Besonders anfällig für Overmocking sind sogenannte Mock-Ketten, bei denen ein Mock ein weiteres Mock zurückgibt, das wiederum ein drittes Mock liefert, um tief verschachtelte Objektgraphen nachzubilden. Solche Konstruktionen, teils abwertend God Mocks genannt, koppeln den Test extrem eng an die konkrete Klassenstruktur und verletzen das Gesetz von Demeter gleich doppelt, einmal im Produktionscode und einmal in dessen Testdouble-Nachbildung. Die Gegenmaßnahme besteht darin, an Systemgrenzen konsequent Fakes statt Mock-Ketten einzusetzen und Mocks ausschließlich für tatsächlich beobachtbare Seiteneffekte an echten Kollaborationsgrenzen zu reservieren, statt für jede interne Methode ein eigenes Mock-Objekt zu konstruieren.
<?php
declare(strict_types=1);
// Simplified pseudocode illustrating the antipattern, not a runnable framework API
// ANTIPATTERN: five collaborators mocked, internal call order verified
final class OvermockedCheckoutTest
{
public function testCheckoutOvermocked(): void
{
$cartMock = $this->createMockCart();
$taxMock = $this->createMockTaxCalculator();
$shippingMock = $this->createMockShippingCalculator();
$inventoryMock = $this->createMockInventoryService();
$paymentMock = $this->createMockPaymentGateway();
// Expectations couple the test to the exact internal call sequence
$cartMock->expectsCall('getItems')->once();
$taxMock->expectsCall('calculate')->once()->after($cartMock);
$shippingMock->expectsCall('calculate')->once()->after($taxMock);
$inventoryMock->expectsCall('reserve')->once()->after($shippingMock);
$paymentMock->expectsCall('charge')->once()->after($inventoryMock);
$checkout = new CheckoutService($cartMock, $taxMock, $shippingMock, $inventoryMock, $paymentMock);
$checkout->process();
// Passes only if internals call collaborators in exactly this order.
// Any refactor that reorders or merges steps breaks the test,
// even if the checkout still produces the correct total.
}
}
// FIXED: a single Fake at the system boundary, verified through the outcome
final class CheckoutServiceTest
{
public function testCheckoutChargesCorrectTotal(): void
{
$paymentGateway = new InMemoryPaymentGateway();
$checkout = new CheckoutService(
new Cart([new CartItem('SKU-1', 2, 25.00)]),
new TaxCalculator(0.19),
new FlatRateShipping(4.90),
new InMemoryInventoryService(),
$paymentGateway
);
$checkout->process();
// Verified through the outcome at the real boundary, not internal call order
$charged = $paymentGateway->getLastChargedAmount(); // 63.40
// No expectation about which collaborator is called first or how often
}
}
8. London School vs. Chicago/Detroit School of TDD
Die London School des Test-Driven Development, auch als mockist-style bekannt, entwickelt Funktionalität konsequent von außen nach innen und ersetzt dabei jede Kollaboration einer Klasse durch ein Mock, noch bevor die eigentliche Implementierung der Kollaboration existiert. Dieser Ansatz erlaubt es, das Design einer Klasse und ihrer Schnittstellen bereits während des Testschreibens zu treiben, weil jede benötigte Interaktion explizit als Erwartung an ein Mock formuliert werden muss, bevor überhaupt Code dafür existiert. Der Vorteil liegt in einer sehr präzisen Isolation jeder einzelnen Einheit und einem Design, das von Anfang an auf klar definierte, austauschbare Schnittstellen ausgelegt ist.
Die Chicago School, auch als klassischer oder state-based Ansatz bezeichnet, testet dagegen bevorzugt mit echten Objekten und setzt Test Doubles nur an den tatsächlichen Systemgrenzen ein, etwa gegenüber Datenbanken oder externen Diensten. Geprüft wird der resultierende Zustand nach der Ausführung, nicht die interne Interaktion zwischen Kollaborateuren. Für Domänenlogik mit vielen zusammenarbeitenden Werteobjekten liefert der Chicago-Ansatz robustere, weniger implementierungsnahe Tests, während sich die London School besonders bei der Entwicklung neuer Schnittstellen zwischen noch unfertigen Komponenten auszahlt. Die meisten produktiven Codebasen profitieren von einer bewussten Mischung, statt sich dogmatisch für eine der beiden Schulen zu entscheiden.
9. Test Doubles im direkten Vergleich
Die folgende Tabelle stellt alle fünf Kategorien der Taxonomie direkt nebeneinander und zeigt für jedes Test Double den Zweck, die Art der Prüfung und ein typisches Einsatzbeispiel. Sie eignet sich als schnelle Entscheidungshilfe, wenn beim Schreiben eines neuen Tests unklar ist, welche Art von Double für eine bestimmte Abhängigkeit die richtige Wahl ist.
| Test Double | Zweck | Prüft | Beispiel-Einsatz |
|---|---|---|---|
| Dummy | Signatur erfüllen, wird nie verwendet | Nichts | Logger-Parameter, der im Test nie aufgerufen wird |
| Stub | Feste, vorgegebene Antworten liefern | Zustand nach Ausführung | StubPriceProvider mit festem Preis |
| Fake | Vereinfachte, echte Implementierung | Zustand über mehrere Aufrufe | InMemoryOrderRepository |
| Spy | Interaktionen aufzeichnen | Verhalten, im Nachhinein geprüft | SpyLogger zeichnet alle Log-Aufrufe auf |
| Mock | Erwartungen vorab definieren | Verhalten, vor der Ausführung erwartet | MockNotifier verifiziert genau einen Aufruf |
Auffällig ist, dass nur Spy und Mock tatsächlich Verhalten verifizieren, während Dummy, Stub und Fake sich auf das Bereitstellen von Zustand beschränken. Wer diese Unterscheidung im Kopf behält, wählt für jede Abhängigkeit bewusst das am wenigsten einschränkende Double, das den Test noch aussagekräftig macht, statt reflexhaft zur mächtigsten verfügbaren Mocking-Strategie zu greifen.
10. Zusammenfassung
Die wichtigste Erkenntnis zur richtigen Mocking-Strategie ist, dass nicht jedes Test Double dieselbe Rolle spielt: Ein Dummy erfüllt nur eine Signatur, ein Stub liefert feste Antworten für Zustandsprüfung, ein Fake bringt vereinfachte, aber echte Logik mit, ein Spy zeichnet Interaktionen ohne vorherige Erwartung auf, und ein Mock verifiziert Verhalten gegen vorab definierte Erwartungen. Wer diese Taxonomie nach Gerard Meszaros konsequent anwendet, vermeidet die häufigste Ursache brüchiger Tests: den Einsatz eines mächtigeren, stärker verhaltensprüfenden Doubles genau dort, wo eine einfache Zustandsprüfung ausreichen würde.
Overmocking, also das Ersetzen jeder einzelnen Abhängigkeit durch ein Mock samt Prüfung interner Aufrufreihenfolgen, ist der zuverlässigste Weg, Tests zu bauen, die bei jedem Refactoring brechen, ohne dass sich am Verhalten der Anwendung etwas geändert hat. Fakes an echten Systemgrenzen, Stubs für einfache Werteanbieter und Mocks ausschließlich für tatsächlich beobachtbare Seiteneffekte ergeben eine Testsuite, die Vertrauen schafft, statt Angst vor dem nächsten Refactoring auszulösen. Ob dabei mit London School oder Chicago School gearbeitet wird, ist zweitrangig gegenüber der Grundregel, für jede Abhängigkeit bewusst das passende Test Double zu wählen.
Test Doubles und Mocking-Strategien, Das Wichtigste auf einen Blick
Taxonomie nach Meszaros
Dummy, Stub, Fake, Spy, Mock: fünf klar abgegrenzte Rollen mit unterschiedlichem Zweck und unterschiedlicher Prüflogik.
Zustand vs. Verhalten
Stub und Fake prüfen Zustand nach der Ausführung, Spy und Mock verifizieren Verhalten während oder nach der Interaktion.
Fakes an Systemgrenzen
In-Memory-Implementierungen wie InMemoryOrderRepository statt Mock-Ketten für zustandsbehaftete Kollaborationen einsetzen.
Overmocking vermeiden
Mocks nur für echte, beobachtbare Seiteneffekte reservieren, nicht für jede interne Methode einer Klasse.
11. FAQ: Test Doubles und Mocking-Strategien
1Was ist ein Test Double?
2Unterschied zwischen Stub und Mock?
3Wann ein Fake statt Mock?
4Unterschied zwischen Spy und Mock?
5Was ist ein Dummy?
6Was bedeutet Overmocking?
7London School vs. Chicago School?
8Ist ein Mocking-Framework Pflicht?
9Woran erkenne ich zu viele Mocks?
10Welches Double für externe APIs?
Mironsoft
Testsuite-Reviews, Testarchitektur und Reduktion brüchiger Mocks
Bremst eure Testsuite jedes Refactoring statt es abzusichern?
Wir analysieren bestehende PHP-Testsuiten auf Overmocking, Mock-Ketten und falsch gewählte Test Doubles, entwickeln eine tragfähige Mocking-Strategie zwischen Stub, Fake, Spy und Mock und richten Tests konsequent an Verhalten statt an Implementierungsdetails aus.
Testsuite-Review
Systematische Prüfung bestehender Tests auf Overmocking, Mock-Ketten und falsch gewählte Test-Double-Kategorien
Testarchitektur
Klare Mocking-Strategie mit Fakes an Systemgrenzen statt fragiler, tief verschachtelter Mock-Konstruktionen
Refactoring-Sicherheit
Tests, die Verhalten statt Implementierungsdetails prüfen und bei internen Umbauten stabil bleiben