Files
everything-claude-code/rules/java/testing.md
Chris Yau 9ceb699e9a feat(rules): add Java language rules (#645)
Adds Java language rules (coding-style, hooks, patterns, security, testing) following the established language rule conventions.
2026-03-19 20:49:21 -07:00

3.4 KiB

paths
paths
**/*.java

Java Testing

This file extends common/testing.md with Java-specific content.

Test Framework

  • JUnit 5 (@Test, @ParameterizedTest, @Nested, @DisplayName)
  • AssertJ for fluent assertions (assertThat(result).isEqualTo(expected))
  • Mockito for mocking dependencies
  • Testcontainers for integration tests requiring databases or services

Test Organization

src/test/java/com/example/app/
  service/           # Unit tests for service layer
  controller/        # Web layer / API tests
  repository/        # Data access tests
  integration/       # Cross-layer integration tests

Mirror the src/main/java package structure in src/test/java.

Unit Test Pattern

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private OrderRepository orderRepository;

    private OrderService orderService;

    @BeforeEach
    void setUp() {
        orderService = new OrderService(orderRepository);
    }

    @Test
    @DisplayName("findById returns order when exists")
    void findById_existingOrder_returnsOrder() {
        var order = new Order(1L, "Alice", BigDecimal.TEN);
        when(orderRepository.findById(1L)).thenReturn(Optional.of(order));

        var result = orderService.findById(1L);

        assertThat(result.customerName()).isEqualTo("Alice");
        verify(orderRepository).findById(1L);
    }

    @Test
    @DisplayName("findById throws when order not found")
    void findById_missingOrder_throws() {
        when(orderRepository.findById(99L)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> orderService.findById(99L))
            .isInstanceOf(OrderNotFoundException.class)
            .hasMessageContaining("99");
    }
}

Parameterized Tests

@ParameterizedTest
@CsvSource({
    "100.00, 10, 90.00",
    "50.00, 0, 50.00",
    "200.00, 25, 150.00"
})
@DisplayName("discount applied correctly")
void applyDiscount(BigDecimal price, int pct, BigDecimal expected) {
    assertThat(PricingUtils.discount(price, pct)).isEqualByComparingTo(expected);
}

Integration Tests

Use Testcontainers for real database integration:

@Testcontainers
class OrderRepositoryIT {

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

    private OrderRepository repository;

    @BeforeEach
    void setUp() {
        var dataSource = new PGSimpleDataSource();
        dataSource.setUrl(postgres.getJdbcUrl());
        dataSource.setUser(postgres.getUsername());
        dataSource.setPassword(postgres.getPassword());
        repository = new JdbcOrderRepository(dataSource);
    }

    @Test
    void save_and_findById() {
        var saved = repository.save(new Order(null, "Bob", BigDecimal.ONE));
        var found = repository.findById(saved.getId());
        assertThat(found).isPresent();
    }
}

For Spring Boot integration tests, see skill: springboot-tdd.

Test Naming

Use descriptive names with @DisplayName:

  • methodName_scenario_expectedBehavior() for method names
  • @DisplayName("human-readable description") for reports

Coverage

  • Target 80%+ line coverage
  • Use JaCoCo for coverage reporting
  • Focus on service and domain logic — skip trivial getters/config classes

References

See skill: springboot-tdd for Spring Boot TDD patterns with MockMvc and Testcontainers. See skill: java-coding-standards for testing expectations.