Test Case: At least two unit test cases for each requirement (and also each sub-requirements) − one positive test and one negative test.
JUnit 5 = JUnit Platform (Launches tests) + JUnit Jupiter (JUnit 5 tests) + JUnit Vintage (legacy JUnit 3 & 4 tests)
@Test
Marks a method as a test case
@BeforeEach
Runs before each test (setup)
@AfterEach
Runs after each test (cleanup)
@BeforeAll
Runs once before all tests (static); initialize shared resources
@AfterAll
Runs once after all tests (static); release shared resources
@DisplayName
Custom name for test display. Example: @DisplayName("Add method works")
@Disabled
Temporarily skip test. Example: @Disabled("waiting for bug fix")
// Checks if two values are equal
assertEquals(expected, actual)
// Checks if two values are not equal
assertNotEquals(a, b)
//Checks if a condition is true
assertTrue(condition)
// Checks if a condition is false
assertFalse(condition)
// Checks if an object is null
assertNull(obj)
// Checks if an object is not null
assertNotNull(obj)
// Verify exception
assertThrows(Exception.class, () -> …)
// Mark test as failed
fail("message")
assertAll(
() -> assertEquals("John", user.getName()),
() -> assertTrue(user.isActive())
);
Allows running a test with multiple inputs.
@ParameterizedTest
@ValueSource(strings = {"racecar", "madam", "level"})
void testPalindrome(String word) {
assertTrue(isPalindrome(word));
}
Other sources:
@CsvSource (multiple params)
@EnumSource (for enums)
@MethodSource (custom data provider)
@RepeatedTest(5)
void testMultipleTimes() { ... }
@Test
@Timeout(1) // seconds
void testShouldFinishQuickly() { ... }
Group multiple test classes together:
import org.junit.platform.suite.api.*;
@Suite
@SelectClasses({ CalculatorTest.class, MathUtilsTest.class })
class AllTestsSuite { }
Grouping tests by category (e.g., fast, slow, integration, unit), so we can include or exclude tags when running tests — useful for large projects and CI pipelines.
@Tag("fast")
@Test
void quickTest() {
// runs quickly
}
@Tag("slow")
@Test
void heavyDatabaseTest() {
// time-consuming operation
}
We can also put tags at the class level:
@Tag("integration")
class UserRepositoryTest {
@Test void testFindById() { ... }
}
Running Tagged Tests
Command line (Maven): mvn test -Dgroups=fast
Unlike normal @Test methods (which are static and fixed at compile time), dynamic tests are generated at runtime — great for testing data-driven or programmatically generated scenarios.
Note: @ParameterizedTest methods are also defined statically at compile time with predefined data sources like @ValueSource, @CsvSource, @EnumSource, etc.
Syntax: A method annotated with @TestFactory returns one or more DynamicTest objects.
import org.junit.jupiter.api.*;
import java.util.stream.*;
import static org.junit.jupiter.api.Assertions.*;
class DynamicTestExample {
@TestFactory
Stream<DynamicTest> dynamicTestsForPalindromes() {
return Stream.of("racecar", "madam", "level", "hello")
.map(word -> DynamicTest.dynamicTest("Testing word: " + word,
() -> assertEquals(isPalindrome(word), word.equals(new StringBuilder(word).reverse().toString()))
));
}
boolean isPalindrome(String s) {
return new StringBuilder(s).reverse().toString().equals(s);
}
}
Output:
✔ Testing word: racecar
✔ Testing word: madam
✔ Testing word: level
✘ Testing word: hello
Allowed return types for @TestFactory:
Collection<DynamicTest>
Stream<DynamicTest>
Iterable<DynamicTest>
Iterator<DynamicTest>