Test Doubles
debt(d7/e5/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints field states automated=no and the code_pattern is 'createMock() on third-party class or concrete class without interface'. No specialist tool in the tooling list catches over-mocking or wrong double type selection — it requires a human reviewer who understands the distinction between mocks, stubs, and fakes, and recognises when implementation details are being over-specified.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests a conceptual swap (createMock vs createStub), but the common_mistakes reveal deeper structural issues: mocking third-party libraries requires introducing adapter layers, and replacing over-mocked tests with behaviour-focused ones (or in-memory fakes) touches many test files and possibly production interfaces. This is more than a single-line fix across a codebase.
Closest to 'persistent productivity tax' (b5). The applies_to scope is cli contexts with PHP 8.0+, but the tags (unit-testing, php) indicate this pattern affects every unit test written. A codebase that adopts over-mocking or incorrect double usage pays a persistent tax: tests become fragile on refactor, give false confidence, and slow down TDD cycles. It doesn't reshape the entire architecture but does impose ongoing drag across many work streams.
Closest to 'serious trap (contradicts how a similar concept works elsewhere)' (t7). The misconception field explicitly states 'more mocks equals better tests' — a belief that seems correct (mocks = isolation = unit tests) but produces the opposite outcome: brittle tests that verify implementation details and break on refactor while giving false confidence. Developers coming from other frameworks or backgrounds naturally reach for mocks everywhere, making this a serious and pervasive cognitive trap.
Also Known As
TL;DR
Explanation
Stub: returns predefined values, no assertions. Mock: verifies interactions (was a method called, how many times, with what args). Spy: records calls for later assertion. Fake: a simplified but working implementation (e.g. in-memory database). Dummy: passed but never used, just to satisfy a parameter. PHPUnit provides mock objects via createMock() and createStub(). Mockery offers a more expressive DSL. Overusing mocks leads to tests that pass even when the real integration is broken.
Common Misconception
Why It Matters
Common Mistakes
- Mocking third-party libraries directly — wrap them in an adapter and mock the adapter instead.
- Using mocks where a fake (in-memory implementation) would give more realistic test coverage.
- Asserting on mock interactions instead of observable outputs — testing how rather than what.
Avoid When
- Do not mock value objects or simple data structures — instantiate them directly.
- Avoid mocking third-party libraries — wrap them in your own interface and mock that instead.
When To Use
- Use stubs when you need a dependency to return a specific value without caring how it was called.
- Use mocks when the interaction itself (method called, args, times) is the behaviour under test.
Code Examples
// Mocking a third-party class directly — fragile, couples to implementation
$guzzle = $this->createMock(\GuzzleHttp\Client::class);
$guzzle->method('request')->willReturn(new Response(200, [], 'ok'));
// Better: mock your own HttpClientInterface that wraps Guzzle
// Stub: provide a return value
$mailer = $this->createStub(MailerInterface::class);
$mailer->method('send')->willReturn(true);
// Mock: verify interaction
$mailer = $this->createMock(MailerInterface::class);
$mailer->expects($this->once())
->method('send')
->with($this->equalTo('user@example.com'));
$service = new UserService($mailer);
$service->registerUser('user@example.com');