If you’re working with Spring, check out "REST With Spring":

>> CHECK OUT THE COURSE

1. Overview

JUnit is one of the most popular unit-testing frameworks in the Java ecosystem. Although the current stable version is JUnit 4.12, a 5.0.0-M2 version contains a number of exciting innovations, with the goal to support new features in Java 8 and above, as well as enabling many different styles of testing.

This article is follow up of our preview of JUnit 5.

2. Maven Dependencies

Installing JUnit 5.0.0-M4 is pretty straightforward, we need to add the following dependency to your pom.xml:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.0.0-M4</version>
    <scope>test</scope>
</dependency>

It is important to note that this version requires Java 8 to work.

What’s more, there is no direct support to run Unit tests on the JUnit Platform in Eclipse yet. You can, of course, run tests using the Maven Test goal inside the IDE.

On the other hand, IntelliJ supports JUnit 5 by default. Therefore, running JUnit 5 on IntelliJ is pretty simple, simply Right click  –> Run, or Ctrl-Shift-F10.

3. Architecture

JUnit 5 is composed of several different modules from three different sub-projects:

3.1. JUnit Platform

It is responsible for launching testing frameworks on the JVM. It defines a stable and powerful interface between JUnit and its client such as build tools. The final objective is how its clients get integrated easily with JUnit in discovering and executing the tests. It also defines the TestEngine API for developing a testing framework that runs on the JUnit platform. By that, you can plug-in 3rd testing libraries, directly into JUnit, by implementing custom TestEngine.

3.2. JUnit Jupiter

This module includes new programming and extension models for writing tests in JUnit 5. New annotations in comparison to JUnit 4 are:

  • @TestFactory – denotes a method that is a test factory for dynamic tests
  • @DisplayName – defines custom display name for a test class or a test method
  • @Nested – denotes that the annotated class is a nested, non-static test class
  • @Tag – declares tags for filtering tests
  • @ExtendWith – it is used to register custom extensions
  • @BeforeEach – denotes that the annotated method will be executed before each test method (previously @Before)
  • @AfterEach – denotes that the annotated method will be executed after each test method (previously @After)
  • @BeforeAll – denotes that the annotated method will be executed before all test methods in the current class (previously @BeforeClass)
  • @AfterAll – denotes that the annotated method will be executed after all test methods in the current class  (previously @AfterClass)
  • @Disable – it is used to disable a test class or method (previously @Ignore)

3.3. JUnit Vintage

Supports running JUnit 3 and JUnit 4 based tests on the JUnit 5 platform.

4. Basic Annotations

To discuss new annotations, we divided the section into the following groups, responsible for execution: before the tests, during the tests (optional) and after the tests:

4.1. @BeforeAll and @BeforeEach

Below is an example of the simple code to be executed before the main test cases:

@BeforeAll
static void setup() {
    log.info("@BeforeAll - executes once before all test methods in this class");
}

@BeforeEach
void init() {
    log.info("@BeforeEach - executes before each test method in this class");
}

Important to note is that the method with @BeforeAll annotation needs to be static, otherwise the code will not compile.

4.2. @DisplayName and @Disabled

Let’s move to new test-optional methods:

@DisplayName("Single test successful")
@Test
void testSingleSuccessTest() {
    log.info("Success");
}

@Test
@Disabled("Not implemented yet")
void testShowSomething() {
}

As we can see, we may change display name or to disable the method with a comment, using new annotations.

4.3. @AfterEach and @AfterAll

Finally, let’s discuss methods connected to operations after tests execution:

@AfterEach
void tearDown() {
    log.info("@AfterEach - executed after each test method.");
}

@AfterAll
static void done() {
    log.info("@AfterAll - executed after all test methods.");
}

Please note that method with @AfterAll needs also to be a static method.

5. Assertions and Assumptions

Please refer to this tutorial on JUnit5.

6. Exception Testing

There are two ways of exception testing in JUnit 5. Both of them can be implemented by using assertThrows() method:

@Test
void shouldThrowException() {
    Throwable exception = assertThrows(UnsupportedOperationException.class, () -> {
      throw new UnsupportedOperationException("Not supported");
    });
    assertEquals(exception.getMessage(), "Not supported");
}

@Test
void assertThrowsException() {
    String str = null;
    assertThrows(IllegalArgumentException.class, () -> {
      Integer.valueOf(str);
    });
}

The first example is used to verify more detail of the thrown exception and the second one just validates the type of exception.

7. Test Suites

To continue the new features of JUnit 5, we will try to get to know the concept of aggregating multiple test classes in a test suite so that we can run those together. JUnit 5 provides two annotations: @SelectPackages and @SelectClasses to create test suites.

Keep in mind that at this early stage most IDEs do not support those features.

Let’s have a look at the first one:

@RunWith(JUnitPlatform.class)
@SelectPackages("com.baeldung")
public class AllTests {}

@SelectPackage is used to specify the names of packages to be selected when running a test suite. In our example, it will run all test. The second annotation, @SelectClasses, is used to specify the classes to be selected when running a test suite:

@RunWith(JUnitPlatform.class)
@SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class})
public class AllTests {}

For example, above class will create a suite contains three test classes. Please note that the classes don’t have to be in one single package.

8. Dynamic Tests

The last topic that we want to introduce is JUnit 5 Dynamic Tests feature, which allows to declare and run test cases generated at run-time. In contrary to the Static Tests which defines fixed number of test cases at the compile time, the Dynamic Tests allow us to define the tests case dynamically in the runtime.

Dynamic tests can be generated by a factory method annotated with @TestFactory. Let’s have a look at the code example:

@TestFactory
public Stream<DynamicTest> translateDynamicTestsFromStream() {
    return in.stream()
      .map(word ->
          DynamicTest.dynamicTest("Test translate " + word, () -> {
            int id = in.indexOf(word);
            assertEquals(out.get(id), translate(word));
          })
    );
}

This example is very straightforward and easy to understand. We want to translate words using two ArrayList, named in and out, respectively. The factory method must return a Stream, Collection, Iterable, or Iterator. In our case, we choose Java 8 Stream. 

Please note that @TestFactory methods must not be private or static. The number of tests is dynamic, and it depends on the ArrayList size.

9. Conclusion

The write-up was a quick overview of the changes that are coming with JUnit 5.

We can see that JUnit 5 has a big change in its architecture which related to platform launcher, integration with build tool, IDE, other Unit test frameworks, etc. Moreover, JUnit 5 is more integrated with Java 8, especially with Lambdas and Stream concepts.

The examples used in this article can be found in the GitHub project.

The new Certification Class of "REST With Spring" is finally out:

>> CHECK OUT THE COURSE