📅 Join me for Acceptance Testing (Live Training) on Wed 25th Feb (17:00 - 19:00 CET) (100% discount for Optivem Journal members)
🔒 Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders & Senior Software Developers apply TDD in Legacy Code.
Many years ago, I used to think every test had to touch everything — the database, other services, payment providers… you name it.
Then I realized: that’s inefficient, feedback loop is too slow. We need tests at different levels.
A user adds a product to their cart, maybe ten times while browsing, and then finally hits checkout, then later cancels their order.
That’s two very different use cases: AddItemToCart and PlaceOrder. And each requires different tests.
The problem: Use Cases and the External World
Most real-world use cases depend on something external:
Databases
Message brokers
External API services
For example, the PlaceOrder use case involves the DB (we need to save orders), it involves message brokers (we publish an event, so that the reporting service can register the newly placed order), and it involves external API services (we need to use PayPal for processing payment for the placed order).
The problem with testing use cases in this way, involving the external world are the following:
The test is too slow due to the I/O
We might not be able to test various logic scenarios
The test might not be able to run if the external system is not available
The solution: Isolation from the External World
In order to be able to test our use case in isolation from the external world:
User-side api: We test the use case via its driving (incoming) port, so that we bypass UI / REST API
Server-side api: We “mock out” the driven (outgoing) ports, so that we’re excluding DB, external APIs, etc. In this way, the use case depends on interfaces of the external world, and doesn’t know (or care) about concrete implementations
This keeps your core logic isolated and maintainable.
Server-side API: Enter Test Doubles
When we write unit tests, those server-side ports are replaced with in-memory test doubles:
Repositories Test Doubles (e.g. Order Repository Test Double)
Gateway Test Doubles (e.g. Notification Test Double, Payment Test Double)
There are different types of Test Doubles: Dummies, Fakes, Stubs, Spies, Mocks (see Fowler’s Test Double article)
The test doubles:
Live entirely in memory
Run ultra fast
Behave predictably
Basically, they let your tests focus on business logic, not “what the database or external API does today.”
Note: The above image is for a microservice, but a similar image can be applied to the frontend. Also, in case of monolithic architecture, similar image can be applied to the monolith.


