Preamble

Test doubles are not all the same creature. Fakes—in-memory repositories, stub message buses—encode expected behavior and stay readable. Mocks assert call sequences and easily couple tests to implementation noise. June is my rule of thumb: fakes at architectural ports, mocks sparingly at edges where you truly need to assert “this retry happened.”


Fakes document contracts

When a fake implements the same interface as a database gateway, the interface becomes honest. Tests read like scenarios: “given these rows, when I place an order, expect this state.” That clarity pays off when polyglot services (Polyglot Interop: HTTP and gRPC Between Python and Java) share schemas.


Mocks and brittleness

Over-mocked tests fail when you rename private methods or reorder internal calls—even though behavior is unchanged. I reserve mocks for protocol concerns: HTTP headers, auth token refresh ordering, idempotency keys.


Architecture tests

Static checks that packages do not import upward—ArchUnit in Java, import-linter in Python—are tests too. They catch boundary violations before review fatigue sets in.


Conclusion

Invest once in good fakes; reuse them across modules. Debugging becomes “swap fake, reproduce,” which pairs with pdb and the Java Debugger: Breakpoints That Teach debugger habits. OpenTelemetry Traces Across Python and Java adds tracing so production matches test assumptions less by hope and more by evidence.