Test Doubles Mocking (Symfony)
Use when
- A class under test depends on a collaborator that is slow, external or non-deterministic, such as a payment gateway or a message bus.
- An interaction must be verified, such as
persist() and flush() or a dispatched message.
- A repository needs an in-memory fake implementing its interface.
- Choosing between a dummy, stub, mock, spy or fake.
Default workflow
- Pick the double by need: stub to feed values, mock to verify a call, fake for a working simplified implementation.
- Create it with
$this->createMock(Interface::class) and stub with ->method('charge')->willReturn(...).
- Verify only when the interaction matters:
expects($this->once())->with(...), or $this->callback() to inspect a dispatched message.
- Script behavior with
willReturnOnConsecutiveCalls(), willThrowException() or willReturnCallback().
- For repositories, write an
InMemory fake implementing the interface.
- Use
ProphecyTrait with prophesize() and reveal() only if the project already relies on Prophecy.
Guardrails
- Do not mock the class under test; a partial mock (
onlyMethods()) is a last resort.
- Mock interfaces, not concrete classes.
- Prefer stubs over mocks, with one mock assertion per test.
- Do not over-mock: behavior that touches the database belongs in a functional test with the real repository.
Progressive disclosure
- Use this file for execution posture and risk controls.
- Open references when deep implementation details are needed.
Output contract
- The double type chosen for each collaborator, and why.
- Interactions verified, limited to those that matter.
- Fakes added under
tests/Fake, if any.
References