# Java Testing

> Testes Java/Spring: TDD, JUnit5, Mockito, TestContainers, @SpringBootTest, @WebMvcTest, ArchUnit. Use ao escrever ou revisar testes, configurar cobertura JaCoCo, ou validar arquitetura com testes automatizados.

- Skill: `nxs-cafi/java-testing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add nxs-cafi/java-testing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nxs-cafi/java-testing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: nxs-cafi (https://skillmd.com/u/nxs-cafi)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/nxs-cafi/java-testing

---


# Testes Java — Spring Boot

## Pirâmide de Testes

```
         /\
        /  \      E2E / API (10-15%)     — @SpringBootTest + TestContainers
       /----\
      /      \    Integration (20-30%)   — @DataJpaTest, @WebMvcTest
     /--------\
    /          \  Unit (55-70%)           — JUnit5 puro, sem Spring context
   /____________\
```

**Por Use Case:** N testes de domínio + 1-2 integração + 1 API (happy + error).

## TDD — Workflow

```
RED → GREEN → REFACTOR
1. Teste que falha descrevendo comportamento
2. Implementação mínima
3. Refatorar sem quebrar testes
```

```java
// RED
@Test
void should_add_amounts_when_same_currency() {
    var price = new Money(new BigDecimal("10.00"), Currency.BRL);
    var tax = new Money(new BigDecimal("1.50"), Currency.BRL);
    assertThat(price.add(tax).amount()).isEqualByComparingTo("11.50");
}

@Test
void should_throw_when_different_currencies() {
    var brl = new Money(new BigDecimal("10"), Currency.BRL);
    var usd = new Money(new BigDecimal("10"), Currency.USD);
    assertThatThrownBy(() -> brl.add(usd)).isInstanceOf(CurrencyMismatchException.class);
}
```

## Nomenclatura

```
should_<resultado>_when_<cenário>
✅ should_throw_not_editable_when_order_is_confirmed
✅ should_return_201_when_create_order_with_valid_payload
❌ testOrder1
```

## Unit Test — Domínio (sem Spring)

```java
@DisplayName("Order.addItem")
class OrderAddItemTest {

    @Test
    void should_add_item_when_order_is_pending() {
        var order = Order.create(CustomerId.random(), List.of());
        var item = OrderItem.of(ProductId.random(), Money.of("50.00", "BRL"), 1);

        order.addItem(item);

        assertThat(order.items()).hasSize(1);
        assertThat(order.total()).isEqualTo(Money.of("50.00", "BRL"));
    }

    @Test
    void should_throw_when_order_is_confirmed() {
        var order = Order.create(CustomerId.random(), List.of());
        order.confirm();

        assertThatThrownBy(() -> order.addItem(OrderItem.random()))
            .isInstanceOf(OrderNotEditableException.class)
            .hasFieldOrPropertyWithValue("orderId", order.id());
    }
}
```

## Mockito — Use Case

```java
@ExtendWith(MockitoExtension.class)
class CreateOrderServiceTest {

    @Mock OrderRepository orderRepository;
    @Mock ApplicationEventPublisher events;
    @InjectMocks CreateOrderService service;

    @Test
    void should_save_order_and_publish_events() {
        var cmd = new CreateOrderCommand(CustomerId.random(), List.of());
        when(orderRepository.save(any())).thenAnswer(i -> i.getArgument(0));

        var result = service.execute(cmd);

        verify(orderRepository).save(any(Order.class));
        verify(events, atLeastOnce()).publishEvent(any());
        assertThat(result.id()).isNotNull();
    }
}
```

**Preferir Fake** para repositories em testes de integração de use case:

```java
class InMemoryOrderRepository implements OrderRepository {
    private final Map<OrderId, Order> store = new ConcurrentHashMap<>();
    @Override public Optional<Order> findById(OrderId id) { return Optional.ofNullable(store.get(id)); }
    @Override public Order save(Order order) { store.put(order.id(), order); return order; }
}
```

## Slice Tests Spring

| Anotação | Escopo | Quando usar |
|----------|--------|-------------|
| (nenhuma) | Classe pura | Domínio, Value Objects |
| `@WebMvcTest` | Controller + MVC | Testar HTTP, status, JSON |
| `@DataJpaTest` | JPA + H2/TC | Queries, mapeamentos |
| `@SpringBootTest` | Contexto completo | Fluxos E2E críticos |

```java
@WebMvcTest(OrderController.class)
class OrderControllerTest {
    @Autowired MockMvc mockMvc;
    @MockBean CreateOrderService createOrderService;

    @Test
    void should_return_201_when_create_order() throws Exception {
        when(createOrderService.execute(any())).thenReturn(OrderResponse.sample());

        mockMvc.perform(post("/api/v1/orders")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""{"customerId":"%s","items":[]}""".formatted(UUID.randomUUID())))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").exists());
    }
}
```

## TestContainers

```java
@SpringBootTest
@Testcontainers
class OrderIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");

    @DynamicPropertySource
    static void configure(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired CreateOrderService createOrderService;
    @Autowired OrderRepository orderRepository;

    @Test
    void should_persist_order_end_to_end() {
        var result = createOrderService.execute(CreateOrderCommand.sample());
        assertThat(orderRepository.findById(result.id())).isPresent();
    }
}
```

## ArchUnit — Testes Arquiteturais

```java
@AnalyzeClasses(packages = "com.company")
class ArchitectureTest {

    @ArchTest
    static final ArchRule domain_should_not_depend_on_infrastructure =
        noClasses().that().resideInAPackage("..domain..")
            .should().dependOnClassesThat().resideInAPackage("..infrastructure..");

    @ArchTest
    static final ArchRule controllers_should_not_access_repositories =
        noClasses().that().resideInAPackage("..interfaces.rest..")
            .should().dependOnClassesThat().resideInAPackage("..infrastructure.persistence..");
}
```

## Cobertura (JaCoCo)

```xml
<!-- pom.xml -->
<plugin>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <configuration>
    <rules>
      <rule>
        <element>BUNDLE</element>
        <limits>
          <limit><counter>LINE</counter><minimum>0.80</minimum></limit>
        </limits>
      </rule>
    </rules>
  </configuration>
</plugin>
```

## Anti-Patterns

| Anti-Pattern | Problema |
|---|---|
| `@SpringBootTest` para tudo | Testes lentos e frágeis |
| Mock de tudo | Não valida integração real |
| `@Transactional` em teste sem rollback | Estado vaza entre testes |
| Testar getters/setters | Sem valor |
| Assert genérico | Mensagem de falha inútil |

## Referências

- `~/.cursor/skills/references/tdd-examples-java.md`

