JUnit 5 Testing
IntermediateJUnit 5 is the modern Java testing framework — annotations, parameterized tests, nested tests, and rich assertions for production-quality test suites.
Overview
JUnit 5 (Jupiter) is the current standard for Java unit testing. It introduces a clean annotation model (@Test, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll), powerful parameterized tests (@ParameterizedTest), nested test classes (@Nested), dynamic tests, and deep integration with build tools and IDEs. Combined with AssertJ for fluent assertions, it makes writing expressive, maintainable tests straightforward.
Core Annotations and Assertions
@Test marks a test method. @BeforeEach / @AfterEach run setup/teardown per test. @BeforeAll / @AfterAll run once per class (must be static). @Disabled skips a test. @DisplayName gives readable names in reports.
JUnit 5 assertions use org.junit.jupiter.api.Assertions. For fluent assertions, AssertJ (assertThat) is strongly preferred.
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calc;
@BeforeEach
void setUp() {
calc = new Calculator(); // fresh instance per test
}
@AfterEach
void tearDown() {
// cleanup if needed
}
@Test
@DisplayName("Adding two positive numbers returns their sum")
void addPositiveNumbers() {
assertEquals(5, calc.add(2, 3));
}
@Test
void divideByZeroThrows() {
assertThrows(ArithmeticException.class,
() -> calc.divide(10, 0));
}
@Test
void multipleAssertions() {
assertAll("calculator",
() -> assertEquals(4, calc.add(2, 2)),
() -> assertEquals(0, calc.subtract(5, 5)),
() -> assertEquals(6, calc.multiply(2, 3))
); // all run even if one fails
}
@Test
@Disabled("Calculator.sqrt not implemented yet")
void sqrt() { /* ... */ }
}Parameterized Tests
@ParameterizedTest runs the same test method with different arguments. Sources: @ValueSource, @CsvSource, @MethodSource, @EnumSource, @NullSource.
This eliminates repetitive test methods and makes edge-case coverage systematic.
import org.junit.jupiter.params.*;
import org.junit.jupiter.params.provider.*;
class StringUtilsTest {
// Simple values
@ParameterizedTest
@ValueSource(strings = {"", " ", "\t", "\n"})
void isBlankReturnsTrueForBlankStrings(String input) {
assertTrue(StringUtils.isBlank(input));
}
// Multiple columns from CSV
@ParameterizedTest
@CsvSource({
"2, 3, 5",
"0, 0, 0",
"-1, 1, 0",
"100, 200, 300"
})
void addReturnsCorrectSum(int a, int b, int expected) {
assertEquals(expected, new Calculator().add(a, b));
}
// From a method (complex objects)
@ParameterizedTest
@MethodSource("provideUsers")
void validateUserSucceeds(User user) {
assertTrue(UserValidator.validate(user));
}
static Stream<User> provideUsers() {
return Stream.of(
new User("Alice", "alice@example.com", 30),
new User("Bob", "bob@example.com", 25)
);
}
// All enum values
@ParameterizedTest
@EnumSource(DayOfWeek.class)
void getDayNameIsNeverNull(DayOfWeek day) {
assertNotNull(DateUtils.getDayName(day));
}
}Nested Tests and AssertJ
@Nested classes group related tests and share setup, making test structure mirror the code structure. AssertJ provides fluent, readable assertions with better error messages than plain JUnit assertions.
import org.assertj.core.api.Assertions.*; // AssertJ
@DisplayName("OrderService")
class OrderServiceTest {
@Nested
@DisplayName("when placing an order")
class PlaceOrder {
private OrderService service;
@BeforeEach
void setUp() { service = new OrderService(mockRepo, mockPayment); }
@Test
@DisplayName("saves the order to the repository")
void savesOrder() {
Order order = service.place(new OrderRequest("item1", 2));
// AssertJ — fluent and readable
assertThat(order)
.isNotNull()
.extracting(Order::getStatus)
.isEqualTo(OrderStatus.PENDING);
}
@Test
void throwsWhenItemOutOfStock() {
assertThatThrownBy(() -> service.place(new OrderRequest("rare", 100)))
.isInstanceOf(OutOfStockException.class)
.hasMessageContaining("rare");
}
}
@Nested
@DisplayName("when cancelling an order")
class CancelOrder {
@Test
void returnsRefundAmount() {
assertThat(service.cancel("order-123"))
.isPositive()
.isLessThanOrEqualTo(originalAmount);
}
}
}Key Points to Remember
- @BeforeEach / @AfterEach run per test method; @BeforeAll / @AfterAll run once per class.
- @ParameterizedTest with @CsvSource or @MethodSource eliminates repetitive test methods.
- assertAll() runs all assertions even if earlier ones fail — better for compound checks.
- @Nested classes group related tests and can have their own @BeforeEach setup.
- AssertJ provides fluent assertions with far better error messages than raw JUnit assertions.
Practice JUnit 5 Testing in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat is the difference between @BeforeEach and @BeforeAll?
How do parameterized tests help with edge case coverage?
What does assertAll() do differently from multiple assertEquals() calls?
How would you test that a method throws a specific exception?
What is the advantage of AssertJ over JUnit's built-in assertions?
Ask Aria about JUnit 5 Testing
Your personal AI tutor — ask anything about this concept