📅 Join me: Clean Architecture for Backend Developers on Wed 26th Aug, 5:00 - 6:30 PM (CEST)
🔒 Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders & Senior Software Developers apply TDD in Legacy Code.
The mistake is to look at the finished use case — availability check, number generation, persistence, a response — and write all of it in one go.
That’s not TDD.
That’s writing the answer and then sprinkling tests on top.
TDD is incremental: write the simplest behavior that could possibly work, get it green.
The next failing test decides what gets added next.
Behavior 1: a guest can reserve a room
Requirement:
“Guest can reserve a room.”
Notice what’s not in that sentence yet: nothing about availability. So we don’t build availability checking. We build the smallest thing the requirement asks for — a reservation gets added, and we get a reservation number back.
🔴 RED — Write the failing test
class PlaceReservationTest {
private FakeReservationRepository reservationRepository;
private StubReservationNumberGenerator reservationNumberGenerator;
private PlaceReservationUseCase placeReservationUseCase;
@BeforeEach
void setUp() {
reservationRepository = fakeReservationRepository(); // fake — in-memory, query state back
reservationNumberGenerator = stubReservationNumberGenerator(); // stub — canned number
placeReservationUseCase = new PlaceReservationUseCase(
reservationRepository, reservationNumberGenerator);
}
@Test
void createsReservation() {
reservationNumberGenerator.returns("RES-123");
var placeReservationRequest = PlaceReservationRequest.builder()
.guestId("guest-1")
.roomId("room-101")
.build();
var response = placeReservationUseCase.execute(placeReservationRequest);
assertThat(response.reservationNumber()).isEqualTo("RES-123");
var reservation = reservationRepository.findByReservationNumber("RES-123");
assertThat(reservation.guestId()).isEqualTo("guest-1");
assertThat(reservation.roomId()).isEqualTo("room-101");
}
}Notice what exists so far:
repository to add the reservation
number generator so the test can pin the returned number (
RES-123) deterministically.
There is no AvailabilityGateway — nothing requires one yet.
Look at the request, too: it only has a guest and a room — no dates. A real reservation obviously has dates, but no behavior here uses them yet, so they’re not in the request yet.
The requirement was “reserve a room,” nothing about when. You add data for the same reason you add code: when a test needs it, not before.
🟢 GREEN — Make it pass with the simplest code
public class PlaceReservationUseCase {
private final ReservationRepository reservationRepository;
private final ReservationNumberGenerator reservationNumberGenerator;
public PlaceReservationUseCase(ReservationRepository reservationRepository,
ReservationNumberGenerator reservationNumberGenerator) {
this.reservationRepository = reservationRepository;
this.reservationNumberGenerator = reservationNumberGenerator;
}
public PlaceReservationResponse execute(PlaceReservationRequest request) {
var reservationNumber = reservationNumberGenerator.next();
reservationRepository.add(new Reservation(
reservationNumber,
request.guestId(),
request.roomId()
));
return new PlaceReservationResponse(reservationNumber);
}
}Green. It always adds the reservation — it never checks anything — because no test has demanded a check yet.
This feels too simple, and that’s the point. You are not allowed to add the availability logic now. There’s no failing test pushing you there.
🔵 REFACTOR — only if you see the need
With the test green, you now get a safe moment to improve the design — rename, extract, remove duplication — without changing behavior. You take it only if you actually see an improvement worth making; if nothing stands out, you skip it and move on.
Here the use case is a few straight-line statements with nothing to tidy, so there's nothing to do.
Behavior 2: only if the room is available
Now a new requirement arrives:
“A room can only be reserved if it is available for the selected dates.”
Now we need to know if the room is available.
🔴 RED — Write the failing test
@Test
void rejectsReservationWhenRoomIsUnavailable() {
availabilityGateway
.forRoom("room-101")
.from("2026-07-10")
.to("2026-07-12")
.returnsUnavailable();
var placeReservationRequest = PlaceReservationRequest.builder()
.guestId("guest-1")
.roomId("room-101")
.from("2026-07-10")
.to("2026-07-12")
.build();
assertThatThrownBy(() -> placeReservationUseCase.execute(placeReservationRequest))
.hasMessage("Room is not available");
}
