Building Testable Software
Anything you don’t fully control is the outside world.
Create a small, dedicated interface for each kind of interaction with the outside world. That can be one interface per external system or per concern (cache, object storage, outbound HTTP, etc.).
Then make sure your application talks to the outside world only through these interfaces, using concrete implementations.
For tests, provide the thinnest possible fake implementation for each interface. It should be stateful, reuse as much logic as is safe from the real one, replace all side effects with in-memory behavior, and offer a few helpers to simulate the external conditions your app cares about (seeding, happy path, partial failures, timeouts, bad responses, etc.).
When production reveals a new failure mode:
- Encode that situation in the fake.
- Write a test that reproduces the bug.
- Fix the code.
Over time, the fake becomes a compact, executable representation of what you’ve learned about the outside world, and your tests document how the application is expected to behave in each situation.