Fundamentals 17 min read

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.

Programmer DD
Programmer DD
Programmer DD
Master JUnit 5: Essential Guide to Java Unit Testing with Real Code Examples

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

software testingtest automationJUnitJUnit 5Java unit testing
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.