What is a JUnit

JUnit is a unit testing framework for Java. The original version was created in 1997 by two master programmers, Kent Beck and Erich Gamma, on a plane trip. Due to the lack of mature tools for Java testing at the time, The two worked together on a prototype JUnit that has evolved through iterations and is now the de facto standard for Java unit testing

JUnit is part of xUnit, a set of testing frameworks based on test-driven development, including:

  • PythonUnit: A unit testing framework for Python
  • CppUnit: unit testing framework for C++
  • JUnit: A unit testing framework for Java

JUnit 5

JUnit 5 is a major upgrade to the JUnit unit testing framework, and requires a Java 8 or higher runtime environment to start with. Although it can compile and run in older JDK versions, the JDK 8 environment is essential to fully use JUnit 5’s capabilities.

In addition, unlike previous versions of JUnit, JUnit 5 is broken down into several different modules of three different subprojects.

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform: A basic service for launching a test framework on the JVM, providing support for executing tests on the command line, IDE, and build tools. JUnit Platform supports not only JUnit’s own test engines, but also other test engines.
  • JUnit Jupiter: Includes JUnit 5’s new programming model and extension model, mainly for writing test and extension code.
  • JUnit Vintage: For test cases compatible with JUnit3.x and JUnit4.x running in JUnit 5.

JUint5 is no longer content with being a unit testing framework. It wants to be a unit testing platform by connecting different test engines to support the use of various test frameworks. Therefore, it also adopts a layered architecture, divided into platform layer, engine layer, framework layer.

Junit 5 architecture and dependencies

The architecture of JUnit 5 is as follows

As long as JUnit’s test engine interface is implemented, any test framework can run on JUnit Platform, which means JUnit5 will be very extensible

JUnit 5The dependencies are as follows

New JUnit 5 features

  • Provides new assertions and test annotations
  • Test class embedding is supported
  • Richer testing methods: support dynamic testing, repeated testing, parameterized testing and so on
  • Modularization is implemented to decoupled different modules such as test execution and test discovery and reduce dependencies
  • Provides support for Java 8, such as Lambda expressions, Sream APIS, and more.

Introduction of depend on

<! -- JUnit 5 API --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.x.x</version> <scope>test</scope> </dependency> <! -- Old JUnit API, need to be introduced if need to be compatible with old API such as JUnit 4, New projects do not need --> <dependency> <groupId>org.junit. Vintage </groupId> <artifactId>junit-vintage </artifactId> <version>5.x.x</version> <scope>test</scope> </dependency>Copy the code

Annotations in JUnit 5

JUnit 5 makes changes and additions to the annotations in JUnit 4

JUnit 4 JUnit 5 Note that
@Test @Test Declare a test method
@BeforeClass @BeforeAll Executes before all test methods of the current class are executed
@AfterClass @AfterAll Executes after all test methods of the current class have been executed
@Before @BeforeEach Execute before each test method executes
@After @AfterEach After each test method is executed, execute
@Ignore @Disabled Disable a test method or test class
@Category @Tag Label test classes or test methods
NA @DisplayName Sets the display name of the test class or test method
NA @RepeatedTest Repeatability test
NA @ ValueSource, @ CsvSource, etc Parametric test
NA @Nested Embed other test classes
NA @TestFactory Use TestFactory for dynamic testing

Examples of common annotations

import org.junit.jupiter.api.*;

@displayName (" Test case ")
class MyTest {

    @BeforeAll
    public static void init(a) {
        System.out.println("Initialize data");
    }

    @AfterAll
    public static void cleanup(a) {
        System.out.println("Clean up the data");
    }

    @BeforeEach
    public void tearup(a) {
        System.out.println("Current test method started");
    }

    @AfterEach
    public void tearDown(a) {
        System.out.println("Current test method ends");
    }

    @ DisplayName (" 1 ")
    @Test
    void testFirstTest(a) {
        System.out.println("Test 1 start test");
    }

    @ DisplayName (" 2 ")
    @Test
    void testSecondTest(a) {
        System.out.println("Test 2 Start test");
    }

    @Disabled
    @ DisplayName (" 3 ")
    @Test
    void testThirdTest(a) {
        System.out.println("Test 3 begin test"); }}Copy the code

The execution result and the function of each annotation are shown below

Pay attention to@BeforeAll@AfterAllAnnotations can only modify static methods

Unlike JUnit 4, JUnit 5’s API comes from the org.junit.jupiter. API package

Repeatability test

Support for setting the number of times a test method can be run has been added to JUnit 5, allowing test methods to be run repeatedly. When you want to run a test method N times, you can flag it with @REPEATedTest

import org.junit.jupiter.api.*;

@displayName (" Test class ")
public class MyTest {

    @displayName (" Repeat test ")
    @RepeatedTest(value = 3)
    public void test(a) {
        System.out.println("Perform tests"); }}Copy the code

We can also change the name of the test method that is repeatedly run by using the built-in variable provided by @REPEATedTest on its name property as a placeholder

@displayName (" Custom name repeat test ")
@repeatedtest (value = 3, name = "{displayName} {currentRepetition} times ")
public void test(a) {
	System.out.println("Perform tests");
}
Copy the code

The @REPEATedTest annotation has the following built-in variables

  • currentRepetitionThe variable represents the number of times it has been repeated
  • totalRepetitionsThe variable represents the total number of repetitions
  • displayNameThe variable represents the test method display name

We can use these built-in variables to redefine the name of the method repeat runtime

Nested test

As the number of classes and code we write grows, so does the number of corresponding test classes we need to test. To address the problem of exploding test classes, JUnit 5 provides the @nested annotation to logically group test case classes in the form of static inner member classes. And each static inner class can have its own lifecycle methods, which are executed hierarchically from the outside in. Also, nested classes can be tagged with @DisplayName so that we can use the correct test name.

import org.junit.jupiter.api.*;

@displayName (" inline test class ")
public class MyTest {

    @Nested
    @displayName (" Inline test class A")
    class FirstNestTest {
        @displayName (" Test method A1")
        @Test
        void test(a) {
            System.out.println("Test Method A1"); }}@Nested
    @displayName (" inline test class B")
    class SecondNestTest {
        @displayName (" Test method B1")
        @Test
        void test(a) {
            System.out.println("Test Method B1");
        }

        @displayName (" test method B2")
        @Test
        void test2(a) {
            System.out.println("Test Method B2"); }}}Copy the code

Parametric test

To use the JUnit 5 parameterized testing feature, you need the Junit-Jupit-Params dependency in addition to the junit-Jupit-engine base dependency

<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>5.x.x</version>  <scope>test</scope> </dependency>Copy the code

ParameterizedTest uses the @parameterizedTest annotation instead of the @test annotation

@ValueSource

@Valuesource is the simplest data argument source provided by JUnit 5. It supports Java’s eight basic types and strings, Class, which are assigned to the corresponding type properties on annotations and passed as arrays

@CsvSource

The @csvSource annotation supports data in CSV format, with a comma as the default delimiter, or can be specified through the DELIMiter property

@CsvFileSource

In addition to writing data directly into code like @csvSource, JUnit 5 also supports @Csvfilesource annotations, specifying external CSV files, specifying resource file paths starting with /, and finding files in the current test resource directory

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;

@displayName (" Test class ")
public class MyTest {

    @ParameterizedTest
    @CsvFileSource(resources = "/test.csv", delimiter = '@' )
    void testDataFromCsv(long id, String name) {
        System.out.printf("id: %d, name: %s", id, name); }}Copy the code

@EnumSource

JUnit 5 also supports data sources of enumeration types, using the @EnumSource annotation to specify an enumeration class, and the @EnumSource annotation provides multiple modes for picking enumeration values, such as excluding DAYS and HOURS in the example below

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import java.util.EnumSet;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE;

@displayName (" Test class ")
public class MyTest {
    @ParameterizedTest
    @EnumSource(value = TimeUnit.class, mode = EXCLUDE, names = { "DAYS", "HOURS" })
    void testWithEnumSourceExclude(TimeUnit timeUnit) { assertFalse(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit)); }}Copy the code

In addition to exclusion patterns, you can also use regular matching patterns

@ParameterizedTest
@EnumSource(value = TimeUnit.class, mode = MATCH_ALL, names = "^(M|N).+SECONDS$")
void testWithEnumSourceExclude(TimeUnit timeUnit) {
    assertFalse(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));
}
Copy the code

@MethodSource

The @methodSource annotation can specify a returned Stream, Array, iterable method as the data source. It is important to note that this method must be static and cannot accept any arguments

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertNotNull;

@displayName (" Test class ")
public class MyTest {
    @ParameterizedTest
    @MethodSource("stringProvider")
    void testWithSimpleMethodSource(String argument) {
        assertNotNull(argument);
    }

    static Stream<String> stringProvider(a) {
        return Stream.of("foo"."bar"); }}Copy the code

The new assertion

JUnit 5 adds some new assertions based on JUnit 4

Timeout assertion: assertTimeoutPreemptively

JUnit 5 introduced assertTimeout, which provides extensive support for timeouts, to test test methods when we want to test the execution time of time-consuming methods and do not want them to wait indefinitely.

Suppose we wanted the test code to execute in a second, we could write the following test case:

@Test
@displayName (" Timeout method test ")
void test_should_complete_in_one_second(a) {
  Assertions.assertTimeoutPreemptively(Duration.of(1, ChronoUnit.SECONDS), () -> Thread.sleep(2000));
}
Copy the code

Exception assertion: assertThrows

For code with an exception thrown, JUnit 5 provides Assertions#assertThrows(Class

, Executable) for testing. The first parameter is an exception type and the second is a functional interface parameter similar to the Runnable interface. No arguments are required, no returns are returned, and Lambda expressions are supported. For details, see the following code:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;


@displayName (" Test class ")
public class MyTest {
    @displayName (" Test caught exception ")
    @Test
    void assertThrowsException(a) {
        String str = null; Assertions.assertThrows(IllegalArgumentException.class, () -> { Integer.valueOf(str); }); }}Copy the code

Maven support for Junit5

Maven’s native support for Junit5 was officially announced on October 24, 2018, when Maven 3.6.0 was released. In this release, the Maven team has released both the Maven Surefire Plugin 2.22.0 and the Maven Failsafe Plugin 2.22.0 to address Junit5 support issues.

Previously, in order to run Junit5 test cases in Maven, an additional Junit Provider provided by the Junit5 team was required for the Maven Surefire Plugin.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.19.1</version>
    <dependencies>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-surefire-provider</artifactId>
            <version>1.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.1.0</version>
        </dependency>
    </dependencies>
</plugin>

Copy the code

When Maven is upgraded to version 3.6.0 or later, the junit-platform-Surefire-provider dependency is no longer required