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-serviceplaceholders with your project’s actual package and service name when applying patterns.
Companion files:
- claude-code-guide.md — index + required reading order.
- ccg-domain-and-ports.md — Steps 1-3 (domain, ports, use case).
- ccg-adapters-and-wiring.md — Step 4 (adapters) + Step 5 (wiring).
- ccg-reference.md — common tasks, anti-patterns, checklists.
- testing.md — full testing strategy.
🐛 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
@Testfun `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}
@Testfun `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
./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")) }}