$npx -y skills add LambdaTest/agent-skills --skill junit-5-skillGenerates 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:
| 1 | # JUnit 5 Testing Skill |
| 2 | |
| 3 | You are a senior Java developer specializing in JUnit 5 testing. |
| 4 | |
| 5 | ## Step 1 — Test Type |
| 6 | |
| 7 | ``` |
| 8 | ├─ "unit test", "assert" → Standard unit test |
| 9 | ├─ "parameterized", "multiple inputs" → @ParameterizedTest |
| 10 | ├─ "mock", "Mockito" → Unit test with Mockito |
| 11 | ├─ "integration test", "Spring" → Read reference/spring-integration.md |
| 12 | └─ Default → Standard unit test |
| 13 | ``` |
| 14 | |
| 15 | ## Core Patterns |
| 16 | |
| 17 | ### Basic Test |
| 18 | |
| 19 | ```java |
| 20 | import org.junit.jupiter.api.*; |
| 21 | import static org.junit.jupiter.api.Assertions.*; |
| 22 | |
| 23 | class CalculatorTest { |
| 24 | private Calculator calculator; |
| 25 | |
| 26 | @BeforeEach |
| 27 | void setUp() { |
| 28 | calculator = new Calculator(); |
| 29 | } |
| 30 | |
| 31 | @Test |
| 32 | @DisplayName("Addition of two positive numbers") |
| 33 | void addPositiveNumbers() { |
| 34 | assertEquals(5, calculator.add(2, 3)); |
| 35 | } |
| 36 | |
| 37 | @Test |
| 38 | void divideByZero_throwsException() { |
| 39 | assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0)); |
| 40 | } |
| 41 | |
| 42 | @Test |
| 43 | void multipleAssertions() { |
| 44 | assertAll("calculator operations", |
| 45 | () -> assertEquals(4, calculator.add(2, 2)), |
| 46 | () -> assertEquals(0, calculator.subtract(2, 2)), |
| 47 | () -> assertEquals(6, calculator.multiply(2, 3)) |
| 48 | ); |
| 49 | } |
| 50 | } |
| 51 | ``` |
| 52 | |
| 53 | ### Assertions Reference |
| 54 | |
| 55 | ```java |
| 56 | assertEquals(expected, actual); |
| 57 | assertNotEquals(unexpected, actual); |
| 58 | assertTrue(condition); |
| 59 | assertFalse(condition); |
| 60 | assertNull(object); |
| 61 | assertNotNull(object); |
| 62 | assertThrows(IllegalArgumentException.class, () -> service.process(null)); |
| 63 | assertTimeout(Duration.ofSeconds(2), () -> service.longRunningOp()); |
| 64 | assertAll("group", |
| 65 | () -> assertNotNull(user.getName()), |
| 66 | () -> assertTrue(user.getAge() > 0) |
| 67 | ); |
| 68 | assertIterableEquals(List.of(1, 2, 3), actualList); |
| 69 | ``` |
| 70 | |
| 71 | ### Parameterized Tests |
| 72 | |
| 73 | ```java |
| 74 | @ParameterizedTest |
| 75 | @ValueSource(strings = {"hello", "world", "junit"}) |
| 76 | void stringIsNotEmpty(String value) { |
| 77 | assertFalse(value.isEmpty()); |
| 78 | } |
| 79 | |
| 80 | @ParameterizedTest |
| 81 | @CsvSource({"1,1,2", "2,3,5", "10,-5,5"}) |
| 82 | void addNumbers(int a, int b, int expected) { |
| 83 | assertEquals(expected, calculator.add(a, b)); |
| 84 | } |
| 85 | |
| 86 | @ParameterizedTest |
| 87 | @MethodSource("provideUsers") |
| 88 | void validateUser(String name, int age, boolean expected) { |
| 89 | assertEquals(expected, validator.isValid(name, age)); |
| 90 | } |
| 91 | |
| 92 | static Stream<Arguments> provideUsers() { |
| 93 | return Stream.of( |
| 94 | Arguments.of("Alice", 25, true), |
| 95 | Arguments.of("", 25, false), |
| 96 | Arguments.of("Bob", -1, false) |
| 97 | ); |
| 98 | } |
| 99 | |
| 100 | @ParameterizedTest |
| 101 | @NullAndEmptySource |
| 102 | @ValueSource(strings = {" ", "\t"}) |
| 103 | void blankStringsAreInvalid(String input) { |
| 104 | assertFalse(validator.isValid(input)); |
| 105 | } |
| 106 | ``` |
| 107 | |
| 108 | ### Mocking with Mockito |
| 109 | |
| 110 | ```java |
| 111 | @ExtendWith(MockitoExtension.class) |
| 112 | class UserServiceTest { |
| 113 | @Mock private UserRepository userRepo; |
| 114 | @Mock private EmailService emailService; |
| 115 | @InjectMocks private UserService userService; |
| 116 | |
| 117 | @Test |
| 118 | void createUser_savesAndSendsEmail() { |
| 119 | User user = new User("alice@test.com", "Alice"); |
| 120 | when(userRepo.save(any(User.class))).thenReturn(user); |
| 121 | |
| 122 | User result = userService.createUser("alice@test.com", "Alice"); |
| 123 | |
| 124 | assertNotNull(result); |
| 125 | verify(userRepo).save(any(User.class)); |
| 126 | verify(emailService).sendWelcomeEmail("alice@test.com"); |
| 127 | } |
| 128 | |
| 129 | @Test |
| 130 | void getUser_notFound_throwsException() { |
| 131 | when(userRepo.findById(99L)).thenReturn(Optional.empty()); |
| 132 | assertThrows(UserNotFoundException.class, () -> userService.getUser(99L)); |
| 133 | } |
| 134 | } |
| 135 | ``` |
| 136 | |
| 137 | ### Nested Tests |
| 138 | |
| 139 | ```java |
| 140 | @DisplayName("UserService") |
| 141 | class UserServiceTest { |
| 142 | @Nested |
| 143 | @DisplayName("when creating a user") |
| 144 | class CreateUser { |
| 145 | @Test void withValidData_succeeds() { } |
| 146 | @Test void withDuplicateEmail_throwsException() { } |
| 147 | } |
| 148 | |
| 149 | @Nested |
| 150 | @DisplayName("when deleting a user") |
| 151 | class DeleteUser { |
| 152 | @Test void existingUser_removesFromDb() { } |
| 153 | @Test void nonExistentUser_throwsException() { } |
| 154 | } |
| 155 | } |
| 156 | ``` |
| 157 | |
| 158 | ### Anti-Patterns |
| 159 | |
| 160 | | Bad | Good | Why | |
| 161 | |-----|------|-----| |
| 162 | | `@Test public void test1()` | `@Test void shouldCalculateSum()` | Descriptive names | |
| 163 | | Testing private methods | Test via public API | Implementation detail | |
| 164 | | No @DisplayName | Always add display names | Better reporting | |
| 165 | | `assertEquals(true, x)` | `assertTrue(x)` | More readable | |
| 166 | |
| 167 | ## Maven Dependencies |
| 168 | |
| 169 | ```xml |
| 170 | <dependency> |
| 171 | <groupId>org.junit.jupiter</groupId> |
| 172 | <artifactId>junit-jupiter</artifactId> |
| 173 | <version>5.11.0</version> |
| 174 | <scope>test</scope> |
| 175 | </dependency> |
| 176 | <dependency> |
| 177 | <groupId>org.mockito</groupI |