Skip to content

29 · 32 · 5 min

Standing in for a real collaborator

A class under test usually depends on something slow, external or unpredictable. A test double replaces it, and there are several kinds people lump together as "mocks".

The kinds

KindDoes
Dummyfills a parameter, never used
Stubreturns canned answers
Fakea real but simplified implementation
Spyrecords what happened, for inspection after
Mockexpects specific calls, fails if they do not happen
java
// stub: supplies an answer
when(rates.forToday()).thenReturn(new Rate("USD", 83.2));

// fake: a working in memory implementation
var repository = new InMemoryOrderRepository();

// mock: asserts an interaction
verify(email).send("[email protected]", "Order confirmed");

Prefer fakes

An in memory repository implementing the same interface is usually better than a stubbed one. It behaves consistently across many tests, it catches misuse a stub would happily accept, and it does not need re-stubbing every time the test changes.

Stubbing every call individually produces tests that restate the implementation, and those break on refactoring.

Verify outcomes, not calls

java
// coupled to how it is done
verify(repository).save(any(Order.class));

// coupled to what happens
placeOrder(order);
assertTrue(repository.findById(order.id()).isPresent());

The second survives a change from save to saveAll. The first does not, while also passing if save was called with the wrong data in a way the matcher tolerates.

The legitimate exception is when the call is the behaviour. That a payment gateway was charged exactly once is a real guarantee, and verifying it is correct.

Do not mock what you do not own

Mocking a third party client encodes your belief about how it behaves. If that belief is wrong, the test passes and production fails, and the test will keep passing after the library changes.

The better approach is a thin adapter of your own around the library, mocked freely, with a small number of integration tests exercising the real thing.

Over mocking

A test with ten mocks is usually telling you the class has ten collaborators. The fix is the class, not the test. And a test where everything is mocked verifies only that the code calls the methods it calls, which is true by construction.