TDD: Unit Tests - Backend (user-side API)
Unit tests define *what* are the outcomes of business logic, not *how* it is implemented.
👉 Reserve Your Spot for the 2026 ATDD Accelerator
When practicing TDD in Hexagonal Architecture, unit tests should target use cases - not internal classes, not frameworks, and not implementation details.
In other words: our tests interact with the business logic as an external observer.
Unit Tests Target the User-Side API
Unit tests talk only to the user-side API, which represents the component’s use cases:
AddItemtoCartRemoveItemFromCartViewCartContentsCalculateCartTotalFilterCartItemsGetProductPrice
The tests know nothing about:
Internal classes
(e.g.Cart,CartItem,PricingCalculator,InventoryChecker)Frameworks
(e.g. Spring, Hibernate, Express, NestJS)Persistence
(e.g. SQL queries, ORM mappings, database schemas)Network calls
(e.g. HTTP requests to inventory or payment services)Technical plumbing
(e.g. transactions, caching, logging, retries)
This is great because it means that we can do refactoring in a safe way.
As long as the use case behaves the same from the outside, we can:
Rename classes
(e.g.CartService→ShoppingCart)Split logic
(e.g. extracting pricing rules into a separate domain object)Merge logic
(e.g. consolidating duplicate validation code)Move code around
(e.g. shifting logic from a service into a domain entity)Change implementations entirely
(e.g. in-memory storage → database, sync calls → async events)
…and our tests remain green ✅
Use Case First, Implementation Later
In TDD, we write a unit test for a specific use case (the test describes behavior in business terms). Only then do we implement the use case (classes and code emerge naturally).
We focus purely on:
Inputs
(e.g.customerId,sku,quantity, passed toAddItemToCart())Outputs
(e.g. cart contains the correct items and quantities, total price is calculated correctly)Observable side effects
(e.g. an exception is thrown when adding an unavailable product, the cart is persisted via the repository interface)
This:
Prevents premature design
(e.g. you don’t designCartItemorPricingCalculatorbefore knowing what behavior is required)Avoids over-engineering
(e.g. you don’t implement caching, events, or complex inventory logic before it’s needed)Forces the design to stay aligned with real usage
(e.g. the system only supports adding/removing items, calculating totals, and viewing the cart exactly as users would do, rather than unnecessary internal implementation details)
Big Picture
The test enters through the same door as a client of the user-side API.
Since we want the test to span the hexagon only, we then have to “mock out“ the server-side API, e.g. using fake adapters.
Real-Life Example: Ordering System
For an e-commerce system, suppose we have a frontend and a backend. We model the backend using Hexagonal Architecture. Let’s see what the user-side API looks like on the backend:


