--- name: unit-testing description: "Writes and maintains unit tests for Java Spring projects using JUnit and Mockito. For testing business logic in services and utilities." --- ## Unit Testing Skill When asked to write or maintain unit tests for a Java Spring workshop, follow these guidelines. ## When to Write Unit Tests - **New services/utilities**: Add tests when creating new service or utility classes. - **Bug fixes**: Include tests that reproduce the bug and assert the fix. - **Complex logic**: Test edge cases, boundary conditions, and error handling. - **Refactoring**: Ensure tests pass before and after refactorings. ## File Naming and Location - **Test source roots**: Put tests under `src/test/java` (and `src/test/resources` for fixtures). - **Naming convention**: Use `ClassNameTest.java` or `ClassNameTests.java` (e.g., `UserServiceTest`). - **Colocate**: Keep the test package mirroring the main source package for easy navigation. ## Test Frameworks and Tools - **Test runner**: JUnit 5 (Jupiter) - **Mocking**: Mockito (use `@ExtendWith(MockitoExtension.class)` or `@MockBean` for Spring tests) - **Assertions**: JUnit assertions plus AssertJ for fluent assertions - **Coverage**: JaCoCo (integrated via Maven or Gradle) ## Test Structure Follow Arrange — Act — Assert. Example structure with JUnit 5 + Mockito: ```java @ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test void createUser_savesSuccessfully() { // Arrange User user = new User("alice@example.com"); when(userRepository.save(any())).thenReturn(user); // Act User result = userService.createUser(user); // Assert assertThat(result.getEmail()).isEqualTo("alice@example.com"); verify(userRepository, times(1)).save(any()); } } ``` For Spring slice tests where you need Spring context, use `@WebMvcTest`, `@DataJpaTest`, or `@SpringBootTest` as appropriate. Prefer lightweight slice tests when possible. ## Mocking Dependencies - Use Mockito for unit tests: `@Mock` + `@InjectMocks` or `Mockito.when(...).thenReturn(...)`. - For Spring integration tests, use `@MockBean` to replace a Spring bean in the context. - Use `ArgumentCaptor` to assert arguments passed to collaborators. Example usage with Mockito: ```java @ExtendWith(MockitoExtension.class) class OrderServiceTest { @Mock OrderRepository repo; @InjectMocks OrderService service; @Test void calculateTotal_usesDiscounts() { when(repo.findById(1L)).thenReturn(Optional.of(new Order(...))); // Act BigDecimal total = service.calculateTotal(1L); // Assert assertThat(total).isEqualByComparingTo(new BigDecimal("42.00")); } } ``` ## Running Tests - **Maven**: `mvn test` (unit tests), `mvn verify` (runs checks and coverage if configured) - **Gradle**: `./gradlew test` - **IDE**: Use IntelliJ/VS Code test runners for faster feedback and debugging - **Coverage report**: `mvn jacoco:report` or `./gradlew jacocoTestReport` Note: Java ecosystem doesn't have a watch mode identical to JS; use IDE test runners or Gradle continuous build (`--continuous`) for faster iteration. ## Coverage Expectations - **Services/utilities**: Aim for >80% on critical modules — prefer meaningful coverage over percentage chasing. - **Edge cases**: Test boundary conditions (nulls, empty lists, max/min values). - **Error paths**: Verify validation, exception handling, and retries. ## Best Practices - **Test behavior, not implementation**: Focus on contract and observable outcomes. - **Small, focused tests**: One logical assertion per test (grouped checks are OK when related). - **Clear test names**: `shouldDoX_whenY()` or `createUser_savesSuccessfully()`. - **Isolated tests**: Use mocks/stubs to avoid external I/O; keep tests fast. - **Use fixtures**: Centralize test data creation (builders, test factories) to reduce duplication. ## Common JUnit/Mockito Patterns - Assertions: `Assertions.assertEquals`, AssertJ `assertThat(...)` - Exception testing: `assertThrows(IllegalArgumentException.class, () -> ...)` - Verify interactions: `verify(mock).method(args)` and `verify(mock, times(1))...` - Argument captors: `@Captor ArgumentCaptor captor; verify(mock).save(captor.capture());` Example: exception assertion ```java @Test void shouldThrow_whenInputInvalid() { assertThrows(IllegalArgumentException.class, () -> service.process(null)); } ``` ## Reference Recommended docs and tools: - JUnit 5: https://junit.org/junit5/ - Mockito: https://site.mockito.org/ - AssertJ: https://assertj.github.io/doc/ - JaCoCo: https://www.eclemma.org/jacoco/ When running a Java Spring workshop, provide example projects with a `pom.xml` or `build.gradle` that include JUnit, Mockito, and JaCoCo so students can run `mvn test` or `./gradlew test` immediately.