Optivem Journal

Optivem Journal

Test Driven Development

TDD: Test the API, NOT the world

Write fast and deterministic tests

Valentina Jemuović's avatar
Valentina Jemuović
Feb 13, 2026
∙ Paid

📅 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.

AddItemToCart vs PlaceOrder (Real-life Example)

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2026 Valentina Jemuović, Optivem · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture