Optivem Journal

Optivem Journal

Test Driven Development

TDD: Do NOT implement all behaviors at once

Code Demo

Valentina Jemuović's avatar
Valentina Jemuović
Jun 05, 2026
∙ Paid

📅 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");
    }

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