Java Testing with JUnit 5
JUnit 5 + AssertJ + Mockito + Testcontainers — opinionated test conventions.
# CLAUDE.md — Java Testing with JUnit 5
## Stack
- **JUnit 5** (Jupiter) for the test runner. No more JUnit 4 in new code.
- **AssertJ** for fluent assertions. Replaces Hamcrest and JUnit's `assertEquals`.
- **Mockito** for mocks. `@Mock` + `@InjectMocks`, or `Mockito.mock(Class)` for fine control.
- **Testcontainers** for real Postgres/Redis/Kafka in tests. SQLite/H2 lie.
## File layout
- Test classes live in `src/test/java`, mirroring `src/main/java` package by package.
- `XxxTest.java` for unit tests, `XxxIT.java` (or `XxxIntegrationTest.java`) for integration. Maven Failsafe runs `*IT` separately.
- One test class per production class. Group by behavior with nested `@Nested` classes.
## JUnit 5 idioms
- `@DisplayName` for human-readable names — they show up in IDE and CI reports.
- `@Nested` to group related cases:
```java
@Nested @DisplayName("when user is admin")
class WhenAdmin { @Test void canDelete() { ... } }
```
- `@ParameterizedTest` with `@MethodSource`, `@ValueSource`, or `@CsvSource`. Don't write copy-paste tests differing only by input.
- `@TestMethodOrder` only when you actually need ordering. Random by default.
## AssertJ
- `assertThat(actual).isEqualTo(expected)`. Don't mix in `assertEquals(...)`.
- Chain assertions: `assertThat(list).hasSize(3).contains("a", "b").doesNotContain("c");`.
- For exceptions, use `assertThatThrownBy(() -> ...)` — message and type in one statement.
- `as("context")` adds a description that shows up in failure messages.
## Mockito
- `@ExtendWith(MockitoExtension.class)` on the class. Use `@Mock`, `@Spy`, `@InjectMocks`.
- Stub with `when(...).thenReturn(...)`. Verify with `verify(mock).method(...)`.
- `lenient()` only when a stub legitimately isn't always called. Otherwise strict mode catches dead stubs.
- Don't mock value types or DTOs. Build them with `new` or a builder.
## Testcontainers
- Start containers with `@Testcontainers` + `@Container`. JUnit 5 manages lifecycle.
- Reuse containers across tests with `withReuse(true)` and the singleton pattern. Boot cost amortizes fast.
- Per-test database: each test runs in a transaction that rolls back. Or truncate in `@BeforeEach`.
## Speed
- Run tests in parallel: `junit.jupiter.execution.parallel.enabled=true`.
- Tag slow tests `@Tag("slow")` and exclude from default runs.
- Profile with `--scan-classpath` and Surefire's `time` reporter.
## Don't
- Don't `Thread.sleep` to wait for async results. Use `CompletableFuture.get(timeout)`, Awaitility, or test scheduling APIs.
- Don't depend on test execution order. Assume any order.
- Don't mock the framework (Spring, Hibernate). Use real instances or thin slice tests.
- Don't write tests that pass with `@Disabled`. Either fix the test or delete it.
Other Java templates
Modern Java Rules
Java 21+ defaults: records, sealed types, pattern matching, virtual threads, var.
Java Virtual Threads & Concurrency
Project Loom virtual threads, structured concurrency, and modern concurrent patterns.
Java Build (Maven & Gradle)
Build conventions, dependency management, multi-module projects, and CI patterns.
Java Streams & Collections
Streams API, immutable collections, Collectors, and idiomatic data manipulation.
Spring Framework Core Rules
Dependency injection, bean lifecycle, configuration classes, and profiles.