Home / Session 4
session-4.md — ~/hacking-with-ai
4

TDD Sprint

Red-Green-Refactor with AI

45 min Test-first workflow Loading...
Session 4 of 8

// Overview

TDD with AI is a superpower. Write integration tests that define the contract for a real endpoint, let Claude implement until green, then refactor with confidence. The ./mvnw verify feedback loop is your best friend.

Learning Objectives

// Setup

Review the existing integration test patterns and IntegrationBase class

bash
cd my-full-app
# Find the IntegrationBase class and existing *IT.java tests
# Review TestControllerIT to understand the IT test pattern
Verify: Understand how IntegrationBase provides Testcontainers, WebTestClient, and database setup

// Challenge

TDD a Currency Conversion Endpoint

25m +15m bonus

Write integration tests for a POST /v1/convert endpoint that converts amounts between currencies using stored exchange rates. Write ALL tests first (they must fail), then let Claude implement everything to make them pass. Use ./mvnw verify 2>&1 | tee run.log as your feedback loop.

✓ Success Criteria

  • CurrencyConversionIT.java written BEFORE any implementation
  • Tests initially fail when running ./mvnw verify (Red phase confirmed)
  • Claude implements controller, service, repository, and DTOs to make tests pass
  • ./mvnw verify passes with all tests green
  • Refactoring step adds validation, rounding, or OpenAPI docs while keeping tests green
⚠ Hints (click to reveal)
  • Start by exploring TestControllerIT to understand the IT pattern
  • Tell Claude: 'Write CurrencyConversionIT.java following the TestControllerIT pattern. DO NOT implement the endpoint yet.'
  • Run ./mvnw verify 2>&1 | tee run.log after each step to confirm RED → GREEN progression
  • Finally: 'Make all tests pass — implement whatever is needed'

Code Playground

Interactive

Try These Prompts

RED — Write the IT test
Look at TestControllerIT and IntegrationBase to understand the integration test pattern. Then write CurrencyConversionIT.java for a POST /v1/convert endpoint that takes { sourceCurrency, targetCurrency, amount } and returns { convertedAmount, rate, sourceCurrency, targetCurrency }. Include tests for: happy path (USD→EUR conversion using a rate seeded in the DB), 404 when currency pair not found, and 401 when unauthenticated. DO NOT write the endpoint implementation — only the test class!
The key is writing tests FIRST — the endpoint doesn't exist yet
Expected: Claude writes a full IT test class following existing patterns, with DB seeding and WebTestClient assertions
RED — Verify tests fail
Run ./mvnw verify 2>&1 | tee run.log — the CurrencyConversionIT tests should fail because the endpoint doesn't exist yet. Confirm we are in the RED phase.
The feedback loop: ./mvnw verify 2>&1 | tee run.log — always pipe to run.log so Claude can read it
Expected: Claude runs verify, confirms compilation or 404 failures, confirms RED phase
GREEN — Make tests pass
Read run.log to see the failures. Implement everything needed to make CurrencyConversionIT pass: controller, service, repository, DTOs, database query — whatever it takes. After each change, run ./mvnw verify 2>&1 | tee run.log and iterate until all tests are green.
Let Claude drive — it will wire up all the layers by reading the test expectations
Expected: Claude implements the full stack, runs verify, fixes failures iteratively until GREEN
REFACTOR — Improve
All tests are green. Now improve the implementation: add input validation (reject negative amounts, blank currencies), proper rounding (2 decimal places for most currencies, 0 for JPY), and OpenAPI annotations (@Operation, @ApiResponse). Run ./mvnw verify 2>&1 | tee run.log after each change to ensure tests stay green. Use /diff to review all uncommitted changes before committing. Then run /simplify to review your changed code for reuse opportunities, quality, and efficiency. If a refactoring breaks tests, use /rewind to restore to the last green checkpoint.
Refactor with confidence — the IT tests catch regressions across all layers. Use /rewind if a refactoring breaks tests — restore to the last green checkpoint. Use /diff to review all changes before committing. Use /simplify as a post-refactor review step.
Expected: Claude adds validation, rounding, and docs while keeping tests green, then /simplify catches further improvements
BONUS — Side questions with /btw
During a long TDD cycle, use /btw to ask quick questions without polluting the test context. For example: '/btw what's the AssertJ method for checking list size?' or '/btw how do I configure Flyway to run a specific migration?'
/btw keeps your main TDD conversation focused — side questions don't derail Claude's context about your test implementation
Expected: Claude answers the side question in a separate context, then you continue the TDD cycle without losing focus
BONUS — Batch operations
Use /batch to run tests across multiple test files or apply a pattern across the codebase. For example: '/batch run ./mvnw verify for each module' or '/batch add OpenAPI annotations to all controller classes'.
/batch is great for applying consistent patterns across multiple files in one go
Expected: Claude processes multiple files or operations in a single batch, saving time on repetitive TDD tasks

Code Examples

IT Test Structure (following TestControllerIT pattern)
// CurrencyConversionIT follows the same pattern as TestControllerIT
// Extends IntegrationBase for Testcontainers + WebTestClient
class CurrencyConversionIT extends IntegrationBase {

    @BeforeEach
    void seedExchangeRates() {
        // Seed the database with known rates for deterministic tests
        jdbcTemplate.execute("""
            INSERT INTO exchange_rates (currency_pair, rate, updated_at)
            VALUES ('USD/EUR', 0.92, NOW())
            ON CONFLICT (currency_pair) DO UPDATE SET rate = 0.92
        """);
    }

    @Test
    void shouldConvertCurrency() {
        webTestClient.post().uri("/v1/convert")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue("""
                {
                    "sourceCurrency": "USD",
                    "targetCurrency": "EUR",
                    "amount": 100.00
                }
            """)
            .exchange()
            .expectStatus().isOk()
            .expectBody()
            .jsonPath("$.convertedAmount").isEqualTo(92.00)
            .jsonPath("$.rate").isEqualTo(0.92)
            .jsonPath("$.sourceCurrency").isEqualTo("USD")
            .jsonPath("$.targetCurrency").isEqualTo("EUR");
    }

    @Test
    void shouldReturn404WhenCurrencyPairNotFound() {
        webTestClient.post().uri("/v1/convert")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue("""
                {
                    "sourceCurrency": "USD",
                    "targetCurrency": "XYZ",
                    "amount": 100.00
                }
            """)
            .exchange()
            .expectStatus().isNotFound();
    }

    @Test
    void shouldReturn401WhenUnauthenticated() {
        // Test without auth token — should be rejected
        unauthenticatedWebTestClient.post().uri("/v1/convert")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue("""
                { "sourceCurrency": "USD", "targetCurrency": "EUR", "amount": 100 }
            """)
            .exchange()
            .expectStatus().isUnauthorized();
    }
}
TDD Cycle with ./mvnw verify
TDD with Claude Code — Integration Test First
================================================

RED Phase:
  "Write CurrencyConversionIT.java. DO NOT implement the endpoint."
  → ./mvnw verify 2>&1 | tee run.log
  → Tests fail (endpoint doesn't exist)
  → This defines the FULL contract: HTTP, JSON, DB, auth

GREEN Phase:
  "Read run.log. Implement everything to make tests pass."
  → Claude creates: Controller, Service, Repository, DTOs
  → ./mvnw verify 2>&1 | tee run.log
  → Iterate until all green

REFACTOR Phase:
  "Add validation, rounding, OpenAPI docs. Keep tests green."
  → ./mvnw verify 2>&1 | tee run.log
  → Tests catch regressions across ALL layers
  → Use /diff to review all uncommitted changes before committing
  → Use /rewind if a refactoring breaks tests — restore to last green state
  → Run /simplify to review changed code for reuse and quality
  → Use /batch to apply patterns across multiple files

The feedback loop:
  ./mvnw verify 2>&1 | tee run.log
  ↑ This is the command. Always pipe to run.log.
  ↑ Claude reads run.log to understand failures.
  ↑ Repeat until green.

Why IT-first TDD + AI is a superpower:
  → One test covers controller + service + DB + auth
  → Claude implements ALL layers from test expectations
  → No mocking — real database, real HTTP, real auth
  → ./mvnw verify is the single source of truth

// Key Takeaways

// Resources

Spring Boot Testing Guide Claude Code Best Practices Checkpointing & /rewind