π Introduction
Testing your code is just as important as writing it. With JUnit 5 and the new features of Java 21, testing has become more powerful, readable, and flexible. This post will help you learn how to set up and write clean, modern tests using JUnit 5 and Java 21 β in the simplest way possible.

π Why JUnit 5?
JUnit 5 is the latest version of the most widely used testing framework in Java. Itβs better than the old JUnit 4 because:
- It uses annotations that are more readable.
- It’s built with modularity, making it easier to extend.
- It supports Java 8+ features like lambdas, streams, and now Java 21 features.
π§° Prerequisites
To follow along, make sure you have:
- Java 21 installed
- Maven or Gradle for dependency management
- An IDE like VS Code or IntelliJ IDEA
βοΈ Maven Setup
Add these dependencies to your pom.xml
:
1 2 3 4 5 6 7 8 9 10 |
<dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope> </dependency> </dependencies> |
π§ͺ Writing Your First Test with JUnit 5
Letβs test a simple method:
1 2 3 4 5 6 7 8 |
public class Calculator { public int add(int a, int b) { return a + b; } } |
Now letβs write a test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class CalculatorTest { @Test void testAddition() { Calculator calc = new Calculator(); assertEquals(5, calc.add(2, 3)); } } |
π What’s Happening?
@Test
tells JUnit to run this method as a test.assertEquals(expected, actual)
checks if the result matches what we expect.
π JUnit 4 vs JUnit 5
Feature | JUnit 4 | JUnit 5 |
---|---|---|
Annotation | @Test | @Test (same) |
Parameterized Tests | External Libs | Built-in |
Dynamic Tests | β Not available | β Supported |
Java 21 Features Support | β Limited | β Excellent |
π§ Java 21 + JUnit 5: Using record
in Tests
Java 21 supports records, which are useful for test data:
1 2 3 4 5 6 7 8 9 |
record Person(String name, int age) {} @Test void testPersonRecord() { Person person = new Person("Alice", 30); assertEquals("Alice", person.name()); } |
π§ͺ Advanced JUnit 5 Features
1. Parameterized Tests
Run a test with multiple inputs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @ParameterizedTest @CsvSource({ "2, 3, 5", "10, 20, 30" }) void testAddWithParameters(int a, int b, int expected) { Calculator calc = new Calculator(); assertEquals(expected, calc.add(a, b)); } |
2. Test Lifecycle with Annotations
1 2 3 4 5 6 7 8 9 10 11 12 |
@BeforeEach void setup() { System.out.println("Before each test"); } @AfterEach void tearDown() { System.out.println("After each test"); } |
π Modern Practices
Practice | Why itβs Good |
---|---|
Use record for test data | Clean and immutable |
Name your tests clearly | Easy to understand |
Use @DisplayName | Makes reports readable |
Keep test methods short | Better maintenance |
Test only one thing per test | Easy to debug |
π§ͺ Structured Concurrency (Java 21) in Testing
You can now write tests for concurrency using structured concurrency:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import java.util.concurrent.StructuredTaskScope; @Test void testWithStructuredConcurrency() throws Exception { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { var task1 = scope.fork(() -> "Hello"); var task2 = scope.fork(() -> "World"); scope.join().throwIfFailed(); assertEquals("Hello", task1.get()); assertEquals("World", task2.get()); } } |
This test checks two parallel tasks using Java 21’s structured concurrency.
β Conclusion
With JUnit 5 and Java 21, testing is cleaner, faster, and more powerful. Even if youβre a beginner, you can write professional-grade tests that help catch bugs early and improve code quality.
So go ahead β write tests and refactor with confidence!
Reference : JUnit 5 User Guide