QA JUnit 5 Writer
Purpose
Write JUnit 5 unit and integration tests from test case specifications. Transform structured test cases into executable JUnit 5 test classes with parameterized tests, nested classes for organization, extensions, Mockito mocking, and AssertJ assertions.
Trigger Phrases
- "Write JUnit 5 tests for [class/feature]"
- "Generate unit tests with JUnit 5"
- "Create parameterized tests for [method]"
- "Add JUnit 5 tests with Mockito"
- "JUnit 5 tests with AssertJ assertions"
- "Nested test classes for [feature]"
- "JUnit 5 @ParameterizedTest for [scenarios]"
- "Mockito mocks in JUnit 5 tests"
- "Heal my failing JUnit 5 tests"
Key Features
| Feature |
Description |
| @Test |
Standard test methods |
| @ParameterizedTest |
Data-driven tests with @CsvSource, @MethodSource, @EnumSource |
| @Nested |
Logical grouping; inner classes with @BeforeEach per level |
| @ExtendWith |
Extensions (MockitoExtension, custom) |
| Mockito |
@Mock, @InjectMocks, when().thenReturn(), verify() |
| AssertJ |
Fluent assertions: assertThat().isEqualTo(), containsExactly() |
| @TestInstance |
PER_CLASS for shared setup; PER_METHOD default |
| @Tag |
Categorization for selective execution |
| @DisplayName |
Human-readable test names |
Workflow
- Read test cases — From specs, requirements, or manual test designs
- Analyze Java code — Inspect classes, methods, dependencies
- Generate test classes — Produce
{Class}Test.java with appropriate structure
- Add mocks/fixtures — Use Mockito for dependencies; fixtures for test data
- Run — User runs
mvn test or ./gradlew test
Key Patterns
| Pattern |
Usage |
@Test |
JUnit 5 test method |
@ParameterizedTest |
Data-driven test |
@CsvSource({"a,1", "b,2"}) |
Inline CSV data |
@MethodSource("provideData") |
Method-provided data |
@Nested |
Group related tests |
@ExtendWith(MockitoExtension.class) |
Enable Mockito |
@Mock |
Mock dependency |
@InjectMocks |
Inject mocks into SUT |
when(mock.method()).thenReturn(value) |
Stub mock |
verify(mock, times(1)).method() |
Verify interaction |
assertThat(actual).isEqualTo(expected) |
AssertJ assertion |
Parameterized Tests
@ParameterizedTest
@CsvSource({"admin, true", "user, false"})
void shouldCheckAccess(String role, boolean expected) {
assertThat(service.hasAccess(role)).isEqualTo(expected);
}
@ParameterizedTest
@MethodSource("provideInvalidInputs")
void shouldRejectInvalidInput(String input) {
assertThatThrownBy(() -> validator.validate(input))
.isInstanceOf(ValidationException.class);
}
Nested Classes
Use @Nested for logical grouping; each nested class gets its own @BeforeEach:
@DisplayName("UserService")
class UserServiceTest {
@Nested
@DisplayName("when user exists")
class WhenUserExists {
@Test
void shouldReturnUser() { ... }
}
@Nested
@DisplayName("when user not found")
class WhenUserNotFound {
@Test
void shouldThrowException() { ... }
}
}
File Naming
{Class}Test.java — Test classes (e.g., UserServiceTest.java, OrderValidatorTest.java)
- Place in
src/test/java mirroring production package structure
Scope
Can do (autonomous):
- Generate JUnit 5 unit and integration tests from test case specs
- Use @ParameterizedTest for data-driven scenarios
- Apply @Nested for logical grouping
- Use Mockito for mocking dependencies
- Use AssertJ for fluent assertions
- Add @Tag, @DisplayName for organization
- Configure @TestInstance when shared setup needed
- Use Context7 MCP for JUnit 5/Mockito docs
- Delegate to qa-test-healer when tests fail (Heal Mode)
Cannot do (requires confirmation):
- Change production code structure
- Add dependencies not in pom.xml/build.gradle
- Override project JUnit/Mockito config without approval
Will not do (out of scope):
- Execute tests (user runs
mvn test)
- Write Spring integration tests (use qa-spring-test-writer)
- Modify CI/CD pipelines
References
references/patterns.md — Parameterized, nested, extensions, lifecycle
references/assertions.md — AssertJ assertion reference
references/config.md — Maven/Gradle JUnit 5 config, Surefire plugin
Quality Checklist
Troubleshooting
| Symptom |
Likely Cause |
Fix |
| Mock not injected |
Wrong extension or order |
Use @ExtendWith(MockitoExtension.class); @InjectMocks before @Mock |
| ParameterizedTest fails |
Invalid CSV/method source |
Check @CsvSource format; ensure @MethodSource returns Stream |
| Nested @BeforeEach runs twice |
Misunderstanding lifecycle |
Each @Nested level has own lifecycle; @BeforeEach runs per test in that level |
| AssertJ import error |
Wrong static import |
Use import static org.assertj.core.api.Assertions.assertThat |
| Test order dependent |
Shared state |
Ensure test isolation; use @TestInstance(PER_METHOD) or fresh fixtures |
| Surefire skips tests |
JUnit 4 engine conflict |
Exclude junit-vintage; use junit-platform-surefire-provider |
1---2name: qa-junit5-writer3description: Generate JUnit 5 unit and integration tests for Java with parameterized tests, nested classes, extensions, Mockito mocking, and AssertJ assertions.4---56# QA JUnit 5 Writer78## Purpose910Write JUnit 5 unit and integration tests from test case specifications. Transform structured test cases into executable JUnit 5 test classes with parameterized tests, nested classes for organization, extensions, Mockito mocking, and AssertJ assertions.1112## Trigger Phrases1314- "Write JUnit 5 tests for [class/feature]"15- "Generate unit tests with JUnit 5"16- "Create parameterized tests for [method]"17- "Add JUnit 5 tests with Mockito"18- "JUnit 5 tests with AssertJ assertions"19- "Nested test classes for [feature]"20- "JUnit 5 @ParameterizedTest for [scenarios]"21- "Mockito mocks in JUnit 5 tests"22- "Heal my failing JUnit 5 tests"2324## Key Features2526| Feature | Description |27| ------- | ----------- |28| **@Test** | Standard test methods |29| **@ParameterizedTest** | Data-driven tests with @CsvSource, @MethodSource, @EnumSource |30| **@Nested** | Logical grouping; inner classes with @BeforeEach per level |31| **@ExtendWith** | Extensions (MockitoExtension, custom) |32| **Mockito** | @Mock, @InjectMocks, when().thenReturn(), verify() |33| **AssertJ** | Fluent assertions: assertThat().isEqualTo(), containsExactly() |34| **@TestInstance** | PER_CLASS for shared setup; PER_METHOD default |35| **@Tag** | Categorization for selective execution |36| **@DisplayName** | Human-readable test names |3738## Workflow39401. **Read test cases** — From specs, requirements, or manual test designs412. **Analyze Java code** — Inspect classes, methods, dependencies423. **Generate test classes** — Produce `{Class}Test.java` with appropriate structure434. **Add mocks/fixtures** — Use Mockito for dependencies; fixtures for test data445. **Run** — User runs `mvn test` or `./gradlew test`4546## Key Patterns4748| Pattern | Usage |49| ------- | ----- |50| `@Test` | JUnit 5 test method |51| `@ParameterizedTest` | Data-driven test |52| `@CsvSource({"a,1", "b,2"})` | Inline CSV data |53| `@MethodSource("provideData")` | Method-provided data |54| `@Nested` | Group related tests |55| `@ExtendWith(MockitoExtension.class)` | Enable Mockito |56| `@Mock` | Mock dependency |57| `@InjectMocks` | Inject mocks into SUT |58| `when(mock.method()).thenReturn(value)` | Stub mock |59| `verify(mock, times(1)).method()` | Verify interaction |60| `assertThat(actual).isEqualTo(expected)` | AssertJ assertion |6162## Parameterized Tests6364```java65@ParameterizedTest66@CsvSource({"admin, true", "user, false"})67void shouldCheckAccess(String role, boolean expected) {68 assertThat(service.hasAccess(role)).isEqualTo(expected);69}7071@ParameterizedTest72@MethodSource("provideInvalidInputs")73void shouldRejectInvalidInput(String input) {74 assertThatThrownBy(() -> validator.validate(input))75 .isInstanceOf(ValidationException.class);76}77```7879## Nested Classes8081Use @Nested for logical grouping; each nested class gets its own @BeforeEach:8283```java84@DisplayName("UserService")85class UserServiceTest {86 @Nested87 @DisplayName("when user exists")88 class WhenUserExists {89 @Test90 void shouldReturnUser() { ... }91 }92 @Nested93 @DisplayName("when user not found")94 class WhenUserNotFound {95 @Test96 void shouldThrowException() { ... }97 }98}99```100101## File Naming102103- `{Class}Test.java` — Test classes (e.g., `UserServiceTest.java`, `OrderValidatorTest.java`)104- Place in `src/test/java` mirroring production package structure105106## Scope107108**Can do (autonomous):**109- Generate JUnit 5 unit and integration tests from test case specs110- Use @ParameterizedTest for data-driven scenarios111- Apply @Nested for logical grouping112- Use Mockito for mocking dependencies113- Use AssertJ for fluent assertions114- Add @Tag, @DisplayName for organization115- Configure @TestInstance when shared setup needed116- Use Context7 MCP for JUnit 5/Mockito docs117- Delegate to qa-test-healer when tests fail (Heal Mode)118119**Cannot do (requires confirmation):**120- Change production code structure121- Add dependencies not in pom.xml/build.gradle122- Override project JUnit/Mockito config without approval123124**Will not do (out of scope):**125- Execute tests (user runs `mvn test`)126- Write Spring integration tests (use qa-spring-test-writer)127- Modify CI/CD pipelines128129## References130131- `references/patterns.md` — Parameterized, nested, extensions, lifecycle132- `references/assertions.md` — AssertJ assertion reference133- `references/config.md` — Maven/Gradle JUnit 5 config, Surefire plugin134135## Quality Checklist136137- [ ] @DisplayName used for readability138- [ ] @ParameterizedTest for multiple inputs; avoid duplicate test methods139- [ ] @Nested for logical grouping where beneficial140- [ ] Mockito used for external dependencies; avoid over-mocking141- [ ] AssertJ used for assertions142- [ ] Tests independent (no shared mutable state)143- [ ] One assertion focus per test where practical144- [ ] Traceability to test case IDs where applicable145- [ ] File naming follows `{Class}Test.java` convention146147## Troubleshooting148149| Symptom | Likely Cause | Fix |150| ------- | ------------ | --- |151| Mock not injected | Wrong extension or order | Use @ExtendWith(MockitoExtension.class); @InjectMocks before @Mock |152| ParameterizedTest fails | Invalid CSV/method source | Check @CsvSource format; ensure @MethodSource returns Stream |153| Nested @BeforeEach runs twice | Misunderstanding lifecycle | Each @Nested level has own lifecycle; @BeforeEach runs per test in that level |154| AssertJ import error | Wrong static import | Use `import static org.assertj.core.api.Assertions.assertThat` |155| Test order dependent | Shared state | Ensure test isolation; use @TestInstance(PER_METHOD) or fresh fixtures |156| Surefire skips tests | JUnit 4 engine conflict | Exclude junit-vintage; use junit-platform-surefire-provider |