# Java Testing

> When to activate: Java testing, JUnit 5, Mockito, AssertJ, Testcontainers, WireMock, Spring MVC test, parameterized tests

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

---

# Java Testing Patterns

## JUnit 5 Basics

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

    @Mock UserRepository userRepository;
    @Mock EmailService emailService;
    @InjectMocks UserService userService;

    @Test
    void findById_returnsUser_whenExists() {
        var user = new User(1L, "Alice", "alice@example.com");
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));

        var result = userService.findById(1L);

        assertThat(result).isEqualTo(user.toDto());
        verify(userRepository).findById(1L);
    }

    @Test
    void findById_throwsNotFound_whenMissing() {
        when(userRepository.findById(anyLong())).thenReturn(Optional.empty());

        assertThatThrownBy(() -> userService.findById(99L))
            .isInstanceOf(ResourceNotFoundException.class)
            .hasMessageContaining("99");
    }

    @Nested
    class CreateUser {
        @Test
        void sendsWelcomeEmail_onSuccess() {
            when(userRepository.existsByEmail(any())).thenReturn(false);
            when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

            userService.create(new CreateUserRequest("Bob", "bob@example.com"));

            verify(emailService).sendWelcome(argThat(u -> "bob@example.com".equals(u.getEmail())));
        }
    }
}
```

## Parameterized Tests

```java
@ParameterizedTest
@CsvSource({
    "valid@email.com, true",
    "invalid-email,   false",
    ",                false",
    "missing@domain,  false"
})
void validateEmail(String email, boolean expected) {
    assertThat(EmailValidator.isValid(email)).isEqualTo(expected);
}

@ParameterizedTest
@MethodSource("provideOrders")
void calculateTotal(List<OrderItem> items, BigDecimal expected) {
    assertThat(orderService.calculateTotal(items)).isEqualByComparingTo(expected);
}

static Stream<Arguments> provideOrders() {
    return Stream.of(
        Arguments.of(List.of(item(10, 2), item(5, 3)), new BigDecimal("35")),
        Arguments.of(List.of(), BigDecimal.ZERO)
    );
}
```

## AssertJ Fluent Assertions

```java
// Collections
assertThat(users)
    .hasSize(3)
    .extracting(User::getName)
    .containsExactlyInAnyOrder("Alice", "Bob", "Charlie");

// Exceptions
assertThatThrownBy(() -> service.process(null))
    .isInstanceOf(IllegalArgumentException.class)
    .hasMessage("Input must not be null");

// Soft assertions — all failures reported at once
SoftAssertions.assertSoftly(soft -> {
    soft.assertThat(user.getName()).isEqualTo("Alice");
    soft.assertThat(user.getEmail()).endsWith("@example.com");
    soft.assertThat(user.isActive()).isTrue();
});
```

## Testcontainers

```java
@SpringBootTest
@Testcontainers
class OrderRepositoryTest {

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

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

    @Autowired OrderRepository repository;

    @Test
    @Transactional
    void savesAndRetrievesOrder() {
        var order = new Order(/* ... */);
        var saved = repository.save(order);
        assertThat(repository.findById(saved.getId())).isPresent();
    }
}
```

## WireMock

```java
@SpringBootTest(webEnvironment = RANDOM_PORT)
@AutoConfigureWireMock(port = 0)
class PaymentClientTest {

    @Autowired PaymentClient paymentClient;

    @Test
    void chargeCard_returnsSuccess() {
        stubFor(post(urlEqualTo("/v1/charges"))
            .withRequestBody(matchingJsonPath("$.amount", equalTo("1000")))
            .willReturn(aResponse()
                .withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBodyFile("charge-success.json")));

        var result = paymentClient.charge(new ChargeRequest("tok_test", 1000));

        assertThat(result.getStatus()).isEqualTo("succeeded");
    }
}
```

## Spring MVC Test

```java
@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired MockMvc mvc;
    @MockBean UserService userService;

    @Test
    void getUser_returns200() throws Exception {
        when(userService.findById(1L)).thenReturn(new UserResponse(1L, "Alice"));

        mvc.perform(get("/api/v1/users/1").accept(APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("Alice"));
    }

    @Test
    void createUser_returns201() throws Exception {
        var body = """{"name":"Bob","email":"bob@example.com"}""";
        when(userService.create(any())).thenReturn(new UserResponse(2L, "Bob"));

        mvc.perform(post("/api/v1/users")
                .contentType(APPLICATION_JSON)
                .content(body))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").value(2));
    }
}
```

## Key Rules
- `@Nested` classes group related tests — use them instead of long method name prefixes
- `@ParameterizedTest` eliminates copy-paste test methods for boundary conditions
- Use `@MockBean` in `@WebMvcTest`/`@SpringBootTest`; use `@Mock` + `@ExtendWith(MockitoExtension.class)` for pure unit tests
- Testcontainers `static` container is reused across test methods in the class — far faster than per-test startup
- AssertJ `extracting()` is cleaner than mapping to a list and then asserting — use it for collection element checks

