Red-Green-Refactor with AI
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.
Review the existing integration test patterns and IntegrationBase class
cd my-full-app
# Find the IntegrationBase class and existing *IT.java tests
# Review TestControllerIT to understand the IT test pattern
Understand how IntegrationBase provides Testcontainers, WebTestClient, and database setup
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.
// 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 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