--- paths: - "**/*.java" --- # Java Testing > This file extends [common/testing.md](../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 ```java @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 ```java @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: ```java @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.