Testing as Design Feedback
Hard-to-test code is usually telling you something about its dependencies. Where to put the boundary, what to double, and when mocks start mirroring implementation.
Code that is miserable to test is usually hiding a dependency or mixing two jobs. That is real information — and it is not a licence to bend the production design around a test runner.
A "unit" is not a class and not a method. It is a boundary you have chosen to hold stable — which makes choosing it a design decision, not a testing convention.
Some abstractions are load-bearing precisely because the thing underneath them is complicated. Replacing those with a double tests your belief about the dependency rather than the dependency.
Mock at boundaries you have chosen to keep stable. Mock every internal collaboration and the suite becomes a cast of the implementation — and then it argues against the refactoring it was supposed to enable.
Stub, fake, mock, spy and dummy are not synonyms. They differ in what they know and therefore in how they fail — and picking the wrong one is how a suite ends up brittle or blind.
A contract test is the thing that keeps a double honest. It is also a design decision: writing one is a declaration that this seam is a contract and not an implementation detail.
When a behaviour can be stated as something true of every input, you can test the statement instead of a handful of examples — and being unable to state one is itself a finding about the design.