Mocking Objects
A real application is full of classes that talk to the outside world — a class that saves an order to a database, one that calls a payment API, one that reads a file from disk. Testing the logic around those calls shouldn't require an actual database connection, a real HTTP request, or real money moving through a payment gateway every time you run your test suite. A mock is a fake stand-in object that lets you test a class in isolation from its real dependencies.
What Mocking Is For
Suppose an Order class depends on a PaymentGatewayInterface to charge a
customer's card. If your test used the real payment gateway, every single test run would attempt an
actual charge — slow, unreliable (what if the network is down?), and potentially costing real money.
Mocking replaces that dependency with a fake object that behaves exactly the way you tell it to,
so the test only exercises the Order class's own logic.
| Without a mock | With a mock |
|---|---|
| Test hits a real database or API | Test uses a fake object that returns exactly what you specify |
| Slow, and fails if the external service is down | Fast and runs the same every time, anywhere |
| Hard to test edge cases (e.g. "what if the API errors?") | Trivial to simulate any return value or exception |
Creating a Mock with createMock()
Inside any test method, $this->createMock(SomeInterface::class) builds a fake object
that implements the given interface (or extends the given class), with every method automatically
stubbed to return null until you configure it otherwise.
<?php
$mock = $this->createMock(PaymentGatewayInterface::class);
Configuring Return Values
Use method() together with willReturn() to tell the mock exactly what to
hand back when a particular method is called — no real lookup ever happens.
<?php
$fakeUser = new User(id: 1, name: 'Jane Doe');
$mock = $this->createMock(UserRepositoryInterface::class);
$mock->method('getUser')
->willReturn($fakeUser);
// Any code that calls $mock->getUser(...) now gets $fakeUser back, instantly
Verifying a Method Was Called
Sometimes what matters isn't a return value but whether a method was called at all — and how many
times. expects() combined with method() turns the mock into an assertion:
the test fails if the expectation isn't met by the time the test finishes.
<?php
$mock->expects($this->once())
->method('save');
// If ->save() is never called, or called more than once, the test fails
Worked Example: Mocking a Payment Gateway
Here's the motivating example in full — an Order class that should call
charge() on its payment gateway exactly once, with the order's total. The test mocks
PaymentGatewayInterface so nothing ever touches a real payment API.
<?php
use PHPUnit\Framework\TestCase;
interface PaymentGatewayInterface
{
public function charge(float $amount): bool;
}
class Order
{
public function __construct(
private PaymentGatewayInterface $gateway,
private float $total
) {}
public function checkout(): bool
{
return $this->gateway->charge($this->total);
}
}
class OrderTest extends TestCase
{
public function testCheckoutChargesTheGatewayForTheOrderTotal()
{
$mockGateway = $this->createMock(PaymentGatewayInterface::class);
$mockGateway->expects($this->once())
->method('charge')
->with(49.99)
->willReturn(true);
$order = new Order($mockGateway, 49.99);
$this->assertTrue($order->checkout());
}
}
This test proves the Order class calls charge() with the right amount,
exactly once — without ever creating a real PaymentGatewayInterface implementation or
contacting a real payment provider.
Unit Tests vs. Integration Tests
The example above is a unit test: it isolates one class (Order) by
replacing its dependency with a mock, so only Order's own logic is under test. An
integration test, by contrast, uses the real dependency — a real database, a real
(sandboxed) API — to verify that the pieces actually work together correctly. Both have their place:
unit tests are fast and pinpoint exactly which class broke; integration tests catch problems that only
show up when real components interact. This page focuses on the unit-testing side, since that's where
mocking does its work.