Over-Mocking
Mocking so much that tests check implementation instead of behavior.
Also known as: mock overuse, over-stubbing
Over-mocking is replacing so many collaborators with test doubles that the test ends up describing the code’s internal wiring rather than its behaviour. The test passes when the code calls the right methods in the right order, not when it produces the right result.
def test_checkout_calls_services():
repo = Mock()
gateway = Mock()
mailer = Mock()
checkout(order, repo, gateway, mailer)
repo.find.assert_called_once()
gateway.charge.assert_called_once()
mailer.send.assert_called_once()
If someone refactors checkout to batch the lookups, this test fails, even though the customer is charged correctly. Meanwhile, a bug that charges the wrong amount still passes, because the mocks don’t check the amount.
The trade-off is isolation against meaning. Doubles make a unit test fast and independent of other code, which is valuable. But each double also encodes an assumption about how the collaborator behaves, and the more of them there are, the more the test proves only that those assumptions are consistent with the code.
The classic mistake is mocking your own internal classes just because they’re easy to replace. Double the real boundaries, such as the network, the clock and third-party services, and let your own logic run for real. Where a test checks outcomes, a fake often reads better than a row of mocks. Keep integration tests that use the real collaborators, so the wiring is tested too.