Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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:...
.claude/skills/sickn33-junit-5-skill/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-15 | ✗→✓ | ▲ Improved | 113% | 0% |
| case-16 | ✓→✓ | = Same ✓ | 82% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 240% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 86% | 0% |
| case-07 | ✓→✓ | = Same ✓ | 82% | 0% |
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.
├─ "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 testjavaimport 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)) ); } }
javaassertEquals(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);
java@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)); }
java@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)); } }
java@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() { } } }
| 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 |
xml<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>
| 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) |
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 |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-16 | pass→pass | 7,744 | 3,697 | -52% | 1 | 1 | 0% | 1,366 | 2,491 | +82% | 0 | 0 | — |
case-06 | pass→pass | 4,525 | 4,825 | +7% | 1 | 1 | 0% | 825 | 2,807 | +240% | 0 | 0 | — |
case-01 | pass→pass | 8,473 | 5,639 | -33% | 1 | 1 | 0% | 1,523 | 2,834 | +86% | 0 | 0 | — |
case-07 | pass→pass | 7,598 | 4,810 | -37% | 1 | 1 | 0% | 1,502 | 2,740 | +82% | 0 | 0 | — |
case-02 | pass→pass | 6,240 | 5,141 | -18% | 1 | 1 | 0% | 1,094 | 2,808 | +157% | 0 | 0 | — |
case-03 | pass→pass | 6,875 | 4,354 | -37% | 1 | 1 | 0% | 1,256 | 2,457 | +96% | 0 | 0 | — |
case-04 | pass→pass | 3,392 | 2,359 | -30% | 1 | 1 | 0% | 516 | 2,110 | +309% | 0 | 0 | — |
case-05 | pass→pass | 4,977 | 4,385 | -12% | 1 | 1 | 0% | 899 | 2,606 | +190% | 0 | 0 | — |
case-08 | pass→pass | 7,410 | 4,122 | -44% | 1 | 1 | 0% | 1,430 | 2,612 | +83% | 0 | 0 | — |
case-09 | pass→pass | 8,376 | 9,646 | +15% | 1 | 1 | 0% | 1,631 | 3,721 | +128% | 0 | 0 | — |
case-10 | pass→pass | 5,451 | 6,958 | +28% | 1 | 1 | 0% | 1,093 | 3,313 | +203% | 0 | 0 | — |
case-11 | pass→pass | 8,386 | 9,571 | +14% | 1 | 1 | 0% | 1,688 | 3,746 | +122% | 0 | 0 | — |
case-12 | pass→pass | 4,390 | 3,384 | -23% | 1 | 1 | 0% | 789 | 2,412 | +206% | 0 | 0 | — |
case-13 | pass→pass | 6,071 | 2,519 | -59% | 1 | 1 | 0% | 1,131 | 2,226 | +97% | 0 | 0 | — |
case-14 | pass→pass | 6,895 | 4,197 | -39% | 1 | 1 | 0% | 1,125 | 2,560 | +128% | 0 | 0 | — |
case-15 | fail→pass | 7,355 | 5,281 | -28% | 1 | 1 | 0% | 1,324 | 2,817 | +113% | 0 | 0 | — |
case-17 | pass→pass | 6,245 | 5,292 | -15% | 1 | 1 | 0% | 1,073 | 2,777 | +159% | 0 | 0 | — |
case-18 | pass→pass | 2,951 | 2,368 | -20% | 1 | 1 | 0% | 571 | 2,205 | +286% | 0 | 0 | — |
case-19 | pass→pass | 4,149 | 2,917 | -30% | 1 | 1 | 0% | 674 | 2,377 | +253% | 0 | 0 | — |
case-20 | pass→pass | 6,661 | 6,376 | -4% | 1 | 1 | 0% | 1,267 | 2,940 | +132% | 0 | 0 | — |
case-21 | pass→pass | 9,727 | 5,274 | -46% | 1 | 1 | 0% | 1,836 | 2,737 | +49% | 0 | 0 | — |
case-22 | pass→pass | 5,429 | 7,170 | +32% | 1 | 1 | 0% | 876 | 3,019 | +245% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.