Saltar al contenido
Mis Guidelines
Guías
En esta guía (8 elementos)

Claude Code Guide — Bug Fixing & Testing Patterns

Claude Code Guide — Bug Fixing & Testing Patterns

Project-agnostic. Part of the Claude Code Guide suite. Covers: bug-fix workflow (identify layer, failing test first, fix, verify), unit test patterns (use case), integration test patterns (repository with Testcontainers).

Replace com.example.platform / platform-service placeholders with your project’s actual package and service name when applying patterns.

Companion files:


🐛 When Fixing Bugs

1. Identify the Layer

Ask: Where does the bug occur?

  • Domain logic bug → Fix in domain entity methods
  • Use case orchestration bug → Fix in use case
  • Data mapping bug → Fix in mapper
  • API validation bug → Fix in REST request DTO
  • Database query bug → Fix in repository adapter
  • Kafka publishing bug → Fix in Kafka adapter

2. Write a Failing Test First

application/src/test/kotlin/usecase/CreateInvoiceUseCaseTest.kt
@Test
fun `should rollback both business write and outbox row when one fails`() {
// Given - outbox save throws inside the TX
val outboxThatFails = mock<OutboxRepositoryPort> {
on { save<Any>(any()) } doThrow RuntimeException("outbox table missing")
}
val useCase = CreateInvoiceUseCase(
transaction, repository, outboxThatFails, outboxEventFactory, clock,
)
// When/Then - the TX rolls back; neither write persists
assertThrows<RuntimeException> { useCase.execute(command) }
verifyTransactionRolledBack(transaction) // helper that asserts callback threw → no commit
}
@Test
fun `should write business row and outbox row inside the same transaction`() {
val captured = AtomicReference<List<String>>(emptyList())
whenever(transaction.run(any())).thenAnswer { inv ->
val ops = mutableListOf<String>()
whenever(repository.save(any())).thenAnswer { ops += "repo.save"; savedInvoice }
whenever(outbox.save<Any>(any())).thenAnswer { ops += "outbox.save"; Unit }
val result = inv.getArgument<() -> Invoice>(0).invoke()
captured.set(ops)
result
}
useCase.execute(command)
assertThat(captured.get()).containsExactly("repo.save", "outbox.save")
}

Key shift: there is no publisher mock and no kafkaTemplate mock. The use case has exactly one event-emission collaborator — OutboxRepositoryPort — and the test asserts both writes belong to the same TX callback.

3. Fix the Bug

4. Verify All Tests Pass

Terminal window
./gradlew test
./gradlew build

🧪 Testing Patterns

Unit Test (Use Case)

class CreateInvoiceUseCaseTest {
private lateinit var transaction: TransactionPort<Invoice>
private lateinit var repository: InvoiceRepositoryPort
private lateinit var outbox: OutboxRepositoryPort
private lateinit var outboxEventFactory: OutboxEventFactory
private lateinit var clock: Clock
private lateinit var useCase: CreateInvoiceUseCase
@BeforeEach
fun setup() {
transaction = mock()
repository = mock()
outbox = mock()
outboxEventFactory = OutboxEventFactory("test-service", EventMessageFactory("test-service"))
clock = Clock.fixed(Instant.parse("2026-01-01T00:00:00Z"), ZoneOffset.UTC)
useCase = CreateInvoiceUseCase(transaction, repository, outbox, outboxEventFactory, clock)
}
@Test
fun `should create invoice and enqueue outbox event`() {
// Given
val command = CreateInvoiceCommand(
organizationId = UUID.randomUUID(),
amount = BigDecimal("100.00"),
currency = "USD",
dueDate = LocalDate.now().plusDays(30),
traceId = "trace-1",
)
val savedInvoice = mock<Invoice>()
whenever(repository.save(any())).thenReturn(savedInvoice)
whenever(transaction.run(any())).thenAnswer { it.getArgument<() -> Invoice>(0).invoke() }
// When
val result = useCase.execute(command)
// Then
assertThat(result).isEqualTo(savedInvoice)
verify(repository).save(any())
verify(outbox).save<EventMessage<InvoiceCreatedPayload>>(argThat { msg ->
msg.topic == "platform.invoices" &&
msg.message.body.eventName == "InvoiceCreated" &&
msg.message.header.traceId == "trace-1"
})
}
}

Integration Test (Repository with Testcontainers)

@SpringBootTest
@Testcontainers
@ActiveProfiles("test")
class InvoiceRepositoryIntegrationTest {
companion object {
@Container
@ServiceConnection
val postgres: PostgreSQLContainer = PostgreSQLContainer("postgres:16-alpine")
}
@Autowired
private lateinit var repository: InvoiceRepositoryPort
@Test
fun `should save and retrieve invoice`() {
// Given
val invoice = Invoice.issue(
id = UUID.randomUUID(),
organizationId = UUID.randomUUID(),
amount = Money(BigDecimal("100.00"), Currency.getInstance("USD")),
dueDate = LocalDate.now().plusDays(30),
clock = Clock.systemUTC()
)
// When
val saved = repository.save(invoice)
val retrieved = repository.findById(saved.id)
// Then
assertThat(retrieved).isNotNull
assertThat(retrieved?.amount?.value).isEqualByComparingTo(BigDecimal("100.00"))
}
}