Test doubles (mock, stub)
Learn to use mocks and stubs to isolate dependencies.
What is a test double?
Some classes depend on outside services: databases, email, payments. Calling them in every test is slow and brittle. A test double is a fake version of that dependency; it keeps tests fast and predictable without real side effects. In PHPUnit you build one with getMockBuilder: $this->getMockBuilder(PaymentGateway::class)->getMock(). A stub only returns canned answers; a mock also checks that calls were made.
Stub vs mock
A stub returns a fixed answer when called: $stub->method('charge')->willReturn(true). It only answers; it does not care whether it was called. A mock also expects calls to happen: $mock->expects($this->once())->method('charge')->with(99.0). If the call never happens, the test fails. Easy mnemonic: a stub speaks, a mock listens. In most cases a stub is enough; reach for a mock only when you must check the interaction.
Exercise Isolate a dependency with a stub
OrderProcessor works with a PaymentGateway dependency. Write a stub named StubPaymentGateway that always returns true. Make OrderProcessorTest extend TestCase. In testCheckoutCallsGateway build an OrderProcessor with the stub, call checkout(99.0) and check the result is true with assertTrue.
Show example solution
bool { return true; } }. Inside class OrderProcessorTest extends TestCase add testCheckoutCallsGateway: $stub = new StubPaymentGateway(); $processor = new OrderProcessor($stub); $this->assertTrue($processor->checkout(99.0));
Example solution:
<?php
// Here we define TestCase ourselves
class TestCase {
protected function assertTrue($condition): void {
if ($condition !== true) { throw new Exception("Condition is not true"); }
}
}
interface PaymentGateway {
public function charge(float $amount): bool;
}
class OrderProcessor {
private PaymentGateway $gateway;
public function __construct(PaymentGateway $gateway) { $this->gateway = $gateway; }
public function checkout(float $amount): bool {
return $this->gateway->charge($amount);
}
}
class StubPaymentGateway implements PaymentGateway {
public function charge(float $amount): bool { return true; }
}
class OrderProcessorTest extends TestCase {
public function testCheckoutCallsGateway(): void {
$stub = new StubPaymentGateway();
$processor = new OrderProcessor($stub);
$this->assertTrue($processor->checkout(99.0));
}
}
// Run the test
$test = new OrderProcessorTest();
$test->testCheckoutCallsGateway();
echo "testCheckoutCallsGateway: PASSED\n";
Test yourself
-
Complete the getMockBuilder call
<?php $gateway = $this->____(PaymentGateway::class) ->disableOriginalConstructor() ->getMock();Show answer
<?php $gateway = $this->getMockBuilder(PaymentGateway::class) ->disableOriginalConstructor() ->getMock(); -
What does getMockBuilder return?
- A mock object directly
- A MockBuilder instance
- The class under test itself
- An Exception
Show answer
A MockBuilder instance — Correct. getMockBuilder returns a MockBuilder; getMock() then yields the mock object.
-
Match the test double types
- Stub
- Returns canned answers
- Mock
- Checks the call
- Real
- Real service
- getMock
- Builds the mock