Master JUnit 5: Essential Guide to Java Unit Testing with Real Code Examples
This article introduces JUnit 5, explains its architecture and key features, and provides step‑by‑step examples of writing, configuring, and running unit tests in Java, covering annotations, lifecycle methods, disabled tests, nested tests, repeated tests, assertions, timeout checks, exception handling, and parameterized testing.
Preface
Unit testing is essential in software development, yet it is often ignored due to tight schedules. Incorporating unit tests can catch many issues early, improve coding skills, and raise project quality. This article introduces the basics of Java unit testing with JUnit 5.
All code snippets are available in the repository: https://github.com/wrcj12138aaa/junit5-actions Supported versions: JDK 8, JUnit 5.5.2, Lombok 1.18.8
Understanding JUnit 5
JUnit 5 is the latest evolution of the Java unit‑testing framework, succeeding JUnit 4 and TestNG. Originating in 1997, JUnit has grown to support Java 8+ features such as lambdas and offers richer test models. JUnit 5 consists of three sub‑projects:
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform : core service for launching testing frameworks on the JVM, providing CLI, IDE, and build‑tool integration.
JUnit Jupiter : new programming and extension model used to write test code.
JUnit Vintage : compatibility layer for running JUnit 3.x and 4.x tests on JUnit 5.
Architecture diagram:
Why Use JUnit 5
Developers demand more testing capabilities: new assertions, nested tests, dynamic, repeated, and parameterized tests, plus better Java 8 support and modular design that reduces dependencies.
New assertions and annotations, support for inner test classes.
Dynamic, repeated, and parameterized testing.
Modular architecture separates execution and discovery.
Full Java 8 support (lambdas, Stream API).
Common JUnit 5 Usage
Add the JUnit 5 dependency to a Maven project (requires Java 8+):
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>First Test Case
A simple test class demonstrates lifecycle annotations and display names:
@DisplayName("我的第一个测试用例")
public class MyFirstTestCaseTest {
@BeforeAll
public static void init() {
System.out.println("初始化数据");
}
@AfterAll
public static void cleanup() {
System.out.println("清理数据");
}
@BeforeEach
public void tearup() {
System.out.println("当前测试方法开始");
}
@AfterEach
public void tearDown() {
System.out.println("当前测试方法结束");
}
@DisplayName("我的第一个测试")
@Test
void testFirstTest() {
System.out.println("我的第一个测试开始测试");
}
@DisplayName("我的第二个测试")
@Test
void testSecondTest() {
System.out.println("我的第二个测试开始测试");
}
}Running this class shows the display names in the test report.
Disabling Tests
Use @Disabled to skip a test method or an entire class:
@DisplayName("我的第三个测试")
@Disabled
@Test
void testThirdTest() {
System.out.println("我的第三个测试开始测试");
}Disabled tests are omitted from execution, as shown in the console output.
Nested Test Classes
Organize related tests with @Nested and give each class its own lifecycle:
@DisplayName("内嵌测试类")
public class NestUnitTest {
@BeforeEach
void init() { System.out.println("测试方法执行前准备"); }
@Nested
@DisplayName("第一个内嵌测试类")
class FirstNestTest {
@Test
void test() { System.out.println("第一个内嵌测试类执行测试"); }
}
@Nested
@DisplayName("第二个内嵌测试类")
class SecondNestTest {
@Test
void test() { System.out.println("第二个内嵌测试类执行测试"); }
}
}Repeated Tests
Mark a method with @RepeatedTest to run it multiple times:
@DisplayName("重复测试")
@RepeatedTest(value = 3)
void i_am_a_repeated_test() {
System.out.println("执行测试");
}Custom names can use placeholders:
@DisplayName("自定义名称重复测试")
@RepeatedTest(value = 3, name = "{displayName} 第 {currentRepetition} 次")
void i_am_a_repeated_test_2() {
System.out.println("执行测试");
}New Assertions
JUnit 5 provides a richer Assertions API that supports lambda expressions for lazy message evaluation and grouped assertions via assertAll:
@Test
void testGroupAssertions() {
int[] numbers = {0,1,2,3,4};
Assertions.assertAll("numbers",
() -> Assertions.assertEquals(1, numbers[1]),
() -> Assertions.assertEquals(3, numbers[3]),
() -> Assertions.assertEquals(4, numbers[4])
);
}Timeout Tests
Use assertTimeoutPreemptively to ensure a test finishes within a given duration:
@Test
@DisplayName("超时方法测试")
void test_should_complete_in_one_second() {
Assertions.assertTimeoutPreemptively(Duration.of(1, ChronoUnit.SECONDS), () -> Thread.sleep(2000));
}The test fails because the simulated work exceeds one second.
Exception Testing
Verify that a specific exception is thrown with assertThrows:
@Test
@DisplayName("测试捕获的异常")
void assertThrowsException() {
String str = null;
Assertions.assertThrows(IllegalArgumentException.class, () -> {
Integer.valueOf(str);
});
}JUnit 5 Parameterized Tests
To run a test with multiple inputs, add the junit-jupiter-params dependency and use @ParameterizedTest together with a source annotation.
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>Value Source
public class ParameterizedUnitTest {
@ParameterizedTest
@ValueSource(ints = {2,4,8})
void testNumberShouldBeEven(int num) {
Assertions.assertEquals(0, num % 2);
}
@ParameterizedTest
@ValueSource(strings = {"Effective Java", "Code Complete", "Clean Code"})
void testPrintTitle(String title) {
System.out.println(title);
}
}CSV Source
@ParameterizedTest
@CsvSource({"1,One", "2,Two", "3,Three"})
void testDataFromCsv(long id, String name) {
System.out.printf("id: %d, name: %s", id, name);
}@CsvFileSource can read external CSV files; the path must start with '/' to locate resources.
Conclusion
With this overview you should now be able to write JUnit 5 unit tests, adopt testing habits, and improve code quality and development efficiency.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
