Write JUnit 5 Tests with AI Coding Assistants
A skill for generating JUnit 5 tests - assertions, parameterized tests, Mockito mocking, and nested test grouping.
Why it matters
Enable AI coding assistants to generate production-grade JUnit 5 test automation code for Java services and applications, with built-in support for Mockito mocking and CI/CD integration through GitHub Actions.
Outcomes
What it gets done
Generate JUnit 5 test classes with proper annotations and assertions for Java services
Create unit tests with Mockito mocks and stubs for dependency isolation
Configure GitHub Actions CI pipelines for automated test execution
Write parameterized and nested test suites following JUnit 5 best practices
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/ag-junit-5-skill | bash Overview
JUnit 5 Testing Skill
This skill generates JUnit 5 tests in Java: assertions, parameterized tests (ValueSource/CsvSource/MethodSource), Mockito mocking, and nested test grouping, with Maven dependencies and anti-pattern guidance. Use it when writing JUnit 5 unit tests, parameterized tests, Mockito-mocked tests, or Spring integration tests in Java.
What it does
A skill for generating production-grade JUnit 5 unit and integration tests in Java, covering assertions, parameterized tests, lifecycle hooks, Mockito mocking, and nested tests. It routes on keywords - "unit test"/"assert" for a standard test, "parameterized"/"multiple inputs" for @ParameterizedTest, "mock"/"Mockito" for a test with mocking, "integration test"/"Spring" pointing to a reference/spring-integration.md doc, defaulting to a standard unit test otherwise. Core patterns show @BeforeEach setup, @Test with @DisplayName, assertThrows for exceptions, and assertAll for grouped assertions that report all failures together rather than stopping at the first. The assertions reference covers assertEquals/assertNotEquals/assertTrue/assertFalse/assertNull/assertNotNull/assertThrows/assertTimeout/assertAll/assertIterableEquals. Parameterized tests cover @ValueSource for a literal list of inputs, @CsvSource for multi-argument rows, @MethodSource pointing to a static Stream<Arguments> provider method, and @NullAndEmptySource combined with @ValueSource for blank-string edge cases. Mockito mocking uses @ExtendWith(MockitoExtension.class) with @Mock fields and @InjectMocks on the class under test, stubbing with when()/thenReturn() and verifying calls with verify(), including a not-found case returning Optional.empty() and asserting the resulting exception. Nested tests use @Nested classes with their own @DisplayName to group related test cases (e.g. "when creating a user" and "when deleting a user" as sibling nested classes). A documented anti-pattern table contrasts non-descriptive test names with descriptive ones, testing private methods versus testing through the public API, omitting @DisplayName versus always adding one, and assertEquals(true, x) versus the more readable assertTrue(x). Maven dependencies are junit-jupiter 5.11.0 and mockito-junit-jupiter 5.14.0, both test-scoped. A quick-reference table covers running all tests (mvn test / gradlew test), a single class or method, tagged tests via @Tag plus -Dgroups, @Disabled with a reason, OS-conditional execution via @EnabledOnOs, @Timeout, @RepeatedTest, and @TestMethodOrder for explicit ordering. A deeper reference/playbook.md covers project setup (Maven deps, parallel config, surefire), test lifecycle (BeforeAll/Each, ordering, tags), deeper parameterized testing (CsvSource, MethodSource, EnumSource, ValueSource), Mockito (captors, verify order), nested and dynamic tests (@TestFactory), and AssertJ fluent assertions.
When to use - and when NOT to
Use it when the user mentions JUnit, JUnit 5, @Test, assertEquals, Assertions, or "Java unit test" - needing standard unit tests, parameterized tests, Mockito-based mocking, or Spring integration tests.
Inputs and outputs
Given Java code to test, it produces a JUnit 5 test class using the appropriate pattern - standard assertions, @ParameterizedTest with the right source annotation, Mockito-mocked dependencies, or @Nested grouping - following the documented naming and anti-pattern guidance.
Integrations
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.11.0</version></dependency>
Uses JUnit 5 (Jupiter) as the test framework, Mockito for mocking dependencies, and integrates with Maven or Gradle for running tests via CLI commands.
Who it's for
Java developers writing JUnit 5 tests - unit tests, parameterized tests across multiple inputs, mocked service tests, or grouped nested test suites - who want correct annotation choice and awareness of common anti-patterns like testing private methods or vague test names.
Source README
JUnit 5 Testing Skill
When to Use
Use this skill when you need generates production-grade JUnit 5 unit and integration tests in Java. Covers assertions, parameterized tests, lifecycle hooks, mocking with Mockito, and nested tests. Use when user mentions "JUnit", "JUnit 5", "@Test", "assertEquals", "Assertions", "Java unit test". Triggers on:...
You are a senior Java developer specializing in JUnit 5 testing.
Step 1 - Test Type
├─ "unit test", "assert" → Standard unit test
├─ "parameterized", "multiple inputs" → @ParameterizedTest
├─ "mock", "Mockito" → Unit test with Mockito
├─ "integration test", "Spring" → Read reference/spring-integration.md
└─ Default → Standard unit test
Core Patterns
Basic Test
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@Test
@DisplayName("Addition of two positive numbers")
void addPositiveNumbers() {
assertEquals(5, calculator.add(2, 3));
}
@Test
void divideByZero_throwsException() {
assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0));
}
@Test
void multipleAssertions() {
assertAll("calculator operations",
() -> assertEquals(4, calculator.add(2, 2)),
() -> assertEquals(0, calculator.subtract(2, 2)),
() -> assertEquals(6, calculator.multiply(2, 3))
);
}
}
Assertions Reference
assertEquals(expected, actual);
assertNotEquals(unexpected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(object);
assertNotNull(object);
assertThrows(IllegalArgumentException.class, () -> service.process(null));
assertTimeout(Duration.ofSeconds(2), () -> service.longRunningOp());
assertAll("group",
() -> assertNotNull(user.getName()),
() -> assertTrue(user.getAge() > 0)
);
assertIterableEquals(List.of(1, 2, 3), actualList);
Parameterized Tests
@ParameterizedTest
@ValueSource(strings = {"hello", "world", "junit"})
void stringIsNotEmpty(String value) {
assertFalse(value.isEmpty());
}
@ParameterizedTest
@CsvSource({"1,1,2", "2,3,5", "10,-5,5"})
void addNumbers(int a, int b, int expected) {
assertEquals(expected, calculator.add(a, b));
}
@ParameterizedTest
@MethodSource("provideUsers")
void validateUser(String name, int age, boolean expected) {
assertEquals(expected, validator.isValid(name, age));
}
static Stream<Arguments> provideUsers() {
return Stream.of(
Arguments.of("Alice", 25, true),
Arguments.of("", 25, false),
Arguments.of("Bob", -1, false)
);
}
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {" ", "\t"})
void blankStringsAreInvalid(String input) {
assertFalse(validator.isValid(input));
}
Mocking with Mockito
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock private UserRepository userRepo;
@Mock private EmailService emailService;
@InjectMocks private UserService userService;
@Test
void createUser_savesAndSendsEmail() {
User user = new User("alice@test.com", "Alice");
when(userRepo.save(any(User.class))).thenReturn(user);
User result = userService.createUser("alice@test.com", "Alice");
assertNotNull(result);
verify(userRepo).save(any(User.class));
verify(emailService).sendWelcomeEmail("alice@test.com");
}
@Test
void getUser_notFound_throwsException() {
when(userRepo.findById(99L)).thenReturn(Optional.empty());
assertThrows(UserNotFoundException.class, () -> userService.getUser(99L));
}
}
Nested Tests
@DisplayName("UserService")
class UserServiceTest {
@Nested
@DisplayName("when creating a user")
class CreateUser {
@Test void withValidData_succeeds() { }
@Test void withDuplicateEmail_throwsException() { }
}
@Nested
@DisplayName("when deleting a user")
class DeleteUser {
@Test void existingUser_removesFromDb() { }
@Test void nonExistentUser_throwsException() { }
}
}
Anti-Patterns
| Bad | Good | Why |
|---|---|---|
@Test public void test1() |
@Test void shouldCalculateSum() |
Descriptive names |
| Testing private methods | Test via public API | Implementation detail |
| No @DisplayName | Always add display names | Better reporting |
assertEquals(true, x) |
assertTrue(x) |
More readable |
Maven Dependencies
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.14.0</version>
<scope>test</scope>
</dependency>
Quick Reference
| Task | Command |
|---|---|
| Run all | mvn test or ./gradlew test |
| Run class | mvn test -Dtest=UserServiceTest |
| Run method | mvn test -Dtest=UserServiceTest#createUser_succeeds |
| Run tagged | @Tag("slow") + mvn test -Dgroups="slow" |
| Disable | @Disabled("Reason") |
| Conditional | @EnabledOnOs(OS.LINUX) |
| Timeout | @Timeout(value = 5, unit = TimeUnit.SECONDS) |
| Repeated | @RepeatedTest(5) |
| Order | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) |
Deep Patterns
For production-grade patterns, see reference/playbook.md:
| Section | What's Inside |
|---|---|
| §1 Project Setup | Maven deps, parallel config, surefire |
| §2 Test Lifecycle | BeforeAll/Each, ordering, tags |
| §3 Parameterized | CsvSource, MethodSource, EnumSource, ValueSource |
| §4 Mockito | @Mock/@InjectMocks, captor, verify order |
| §5 Nested & Dynamic | @Nested grouping, @TestFactory |
| §6 AssertJ | Fluent assertions, extracting, collection checks |
| §7 Conditional | @EnabledOnOs, assumptions, @EnabledIf |
| §8 Custom Extensions | Timing, retry, BeforeTestExecution |
| §9 CI/CD | GitHub Actions with test reporter |
| §10 Debugging Table | 8 common problems with fixes |
| §11 Best Practices | 12-item production checklist |
Limitations
- Use this skill only when the task clearly matches its upstream source and local project context.
- Verify commands, generated code, dependencies, credentials, and external service behavior before applying changes.
- Do not treat examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.