Introduction to JUnit 5

After we have finished this webinar, we

  • Can get the required dependencies with Maven and Gradle.
  • Can run our unit tests with Maven and Gradle.
  • Are familiar with the basic annotations supported by JUnit 5
  • Can use setup and teardown methods.
  • Know how we can write test methods with JUnit 5.

Creating a Maven Project

We can create a Maven build that runs our unit tests by following these steps:

First, we have to get the required dependencies. We can do this by declaring the junit-jupiter-api (version 5.0.0-M6) dependency in our pom.xml file. This dependency provides the public API for writing tests and extensions.

Also, if we are using an IDE that is bundled with an older JUnit 5 version AND we want to run our unit tests by using our IDE, we have to also add the following dependencies to our POM file:

  • The junit-jupiter-engine (version 5.0.0-M6) dependency contains the implementation of the JUnit Jupiter test engine. This dependency is required only at runtime.
  • The junit-vintage-engine (version 4.12.0-M6) dependency adds support for unit tests that use JUnit 4 or JUnit 3.
  • The junit-platform-launcher (version 1.0.0-M6) dependency provides a public API for configuring and launching tests. This API is typically used by IDEs and build tools.
  • The junit-platform-runner (version 1.0.0-M6) dependency allows us to run tests and test suites in a JUnit 4 environment.

We can add the required dependencies to our POM file by adding the following snippet to the dependencies section of our POM file:

 1<dependency>
 2	<groupId>org.junit.jupiter</groupId>
 3	<artifactId>junit-jupiter-api</artifactId>
 4	<version>5.0.0-M6</version>
 5	<scope>test</scope>
 6</dependency>
 7<dependency>
 8    <groupId>org.junit.jupiter</groupId>
 9    <artifactId>junit-jupiter-engine</artifactId>
10    <version>5.0.0-M6</version>
11    <scope>test</scope>
12</dependency>
13<dependency>
14    <groupId>org.junit.vintage</groupId>
15    <artifactId>junit-vintage-engine</artifactId>
16    <version>4.12.0-M6</version>
17    <scope>test</scope>
18</dependency>
19<dependency>
20    <groupId>org.junit.platform</groupId>
21    <artifactId>junit-platform-launcher</artifactId>
22    <version>1.0.0-M6</version>
23    <scope>test</scope>
24</dependency>
25<dependency>
26    <groupId>org.junit.platform</groupId>
27    <artifactId>junit-platform-runner</artifactId>
28    <version>1.0.0-M6</version>
29    <scope>test</scope>
30</dependency>

Second, we have to declare the maven-surefire-plugin (version 2.19.1) in our pom.xml file and configure the dependencies of this plugin. We have to declare the following dependencies:

  • The junit-platform-surefire-provider (version 1.0.0-M6) dependency allows us to run tests that use either the “old” JUnit (3 or 4) or JUnit 5.
  • If we want to run tests that use either JUnit 3 or 4, we have to declare the junit-vintage-engine (version 4.12.0-M6) dependency. That being said, because we have already declared it, we don’t have to declare this dependency for the second time.
  • If we want to run tests that use JUnit 5, we have to declare the junit-jupiter-engine (version 5.0.0-M6) dependency. However, because we have already declared it, we don’t have to declare this dependency for the second time.

We can declare the maven-surefire-plugin and configure its dependencies by adding the following snippet to the plugins section of our POM file:

 1<plugin>
 2	<groupId>org.apache.maven.plugins</groupId>
 3	<artifactId>maven-surefire-plugin</artifactId>
 4	<version>2.19.1</version>
 5	<dependencies>
 6		<dependency>
 7			<groupId>org.junit.platform</groupId>
 8			<artifactId>junit-platform-surefire-provider</artifactId>
 9			<version>1.0.0-M6</version>
10		</dependency>
11	</dependencies>
12</plugin>

I think that we should configure the runtime dependencies when we declare the dependencies of the maven-surefire-plugin. This allows us to separate the compile-time and runtime dependencies. However, we cannot do this if our IDE is bundled with an older JUnit 5 version.

Additional Reading:

Next, we will find out how we can create a Gradle project that can run unit tests which use JUnit 5.

Creating a Gradle Project

We can create a Gradle project that runs our unit tests by following these steps:

First, we have to configure the dependencies of our build script. We can do this by adding the junit-platform-gradle-plugin dependency to the classpath configuration.

After we have done this, the source code of our build.gradle file looks as follows:

1buildscript {
2    repositories {
3        mavenCentral()
4    }
5    dependencies {
6        classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-M6'
7    }
8}

Second, we have to apply the JUnit Gradle plugin. After we have applied this plugin, the source code of our build.gradle file looks as follows:

 1buildscript {
 2    repositories {
 3        mavenCentral()
 4    }
 5    dependencies {
 6        classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-M6'
 7    }
 8}
 9
10apply plugin: 'java'
11apply plugin: 'org.junit.platform.gradle.plugin'

Third, we have to get the required dependencies. We can do this by following these steps:

  1. Add the junit-jupiter-api (version 5.0.0-M6) dependency to the testCompile dependency configuration. This dependency provides the public API for writing tests and extensions.
  2. Add the used test engine implementations to the testRuntime configuration. Because we want to run only tests that use JUnit 5, we have to add the junit-jupiter-engine (version 5.0.0-M6) dependency to the testRuntime dependency configuration.

Also, if we are using an IDE that is bundled with an older JUnit 5 version AND we want to run our unit tests by using our IDE, we have to also add the following dependencies to the testRuntime dependency configuration:

  • The junit-vintage-engine (version 4.12.0-M6) dependency adds support for unit tests that use JUnit 4 or JUnit 3.
  • The junit-platform-launcher (version 1.0.0-M6) dependency provides a public API for configuring and launching tests. This API is typically used by IDEs and build tools.
  • The junit-platform-runner (version 1.0.0-M6) dependency allows us to run tests and test suites in a JUnit 4 environment.

After we have configured the required dependencies, our build.gradle file looks as follows:

 1buildscript {
 2    repositories {
 3        mavenCentral()
 4    }
 5    dependencies {
 6        classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-M6'
 7    }
 8}
 9
10apply plugin: 'java'
11apply plugin: 'org.junit.platform.gradle.plugin'
12
13repositories {
14    mavenCentral()
15}
16
17dependencies {
18    testCompile(
19            'org.junit.jupiter:junit-jupiter-api:5.0.0-M6'
20    )
21    testRuntime(
22            'org.junit.jupiter:junit-jupiter-engine:5.0.0-M6',
23            'org.junit.vintage:junit-vintage-engine:4.12.0-M6',
24            'org.junit.platform:junit-platform-launcher:1.0.0-M6',
25            'org.junit.platform:junit-platform-runner:1.0.0-M6'
26    )
27}

Fourth, we have to ensure that Gradle prints the information that is written to System.out or System.err when our unit tests are run. This is not required, but it allows us to see the order in which our setup, teardown, and test methods are run.

After we have made the required changes to the configuration of the test task, the source code of our build.gradle file looks as follows:

 1buildscript {
 2    repositories {
 3        mavenCentral()
 4    }
 5    dependencies {
 6        classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-M6'
 7    }
 8}
 9
10apply plugin: 'java'
11apply plugin: 'org.junit.platform.gradle.plugin'
12
13repositories {
14    mavenCentral()
15}
16
17dependencies {
18    testCompile(
19            'org.junit.jupiter:junit-jupiter-api:5.0.0-M6'
20    )
21    testRuntime(
22            'org.junit.jupiter:junit-jupiter-engine:5.0.0-M6',
23            'org.junit.vintage:junit-vintage-engine:4.12.0-M6',
24            'org.junit.platform:junit-platform-launcher:1.0.0-M6',
25            'org.junit.platform:junit-platform-runner:1.0.0-M6'
26    )
27}
28
29test {
30    testLogging {
31        showStandardStreams = true
32    }
33}

Let’s move on and write our first test class.

Writing Our First Test Class

A test class that uses JUnit 5 is just a regular Java class. That being said, JUnit 5 supports package private test classes. We can create our first class test class by following these steps:

  1. Create a new package private class.
  2. Annotate the class with the @DisplayName annotation and configure the display name of our test class. This is an optional step, but I recommend that we should do this because the @DisplayName annotation allows us to use real sentences instead of technical names. If we specify a custom display name, our IDE and build tool will use our custom display name when they create or show test reports.

After we have created our first test class, its source code looks as follows:

1import org.junit.jupiter.api.DisplayName;
2
3@DisplayName("JUnit 5 Example")
4class JUnit5ExampleTest {
5
6}

We can add the @DisplayName annotation to a test class or to a test method.

Additional Reading:

We have just created our first test class. Our next step is to add setup and teardown methods to our test class.

Using Setup and Teardown Methods

Setup and teardown methods are package private methods that don’t return anything and don’t have any method parameters. These methods are invoked either before or after the methods which are annotated with one of the following annotations: @Test, @RepeatedTest, @ParameterizedTest, or @TestFactory.

Because I don’t want to repeat myself, I will use the term test method in this section even though it is not 100% accurate.

Let’s add all setup and teardown methods to our test class. We can do this by following these steps:

  1. Add a static beforeAll() method to our test class and annotate this method with @BeforeAll annotation. This method is invoked once before any test method is run and this method is inherited.
  2. Add a beforeEach() method to our test class and annotate this method with the @BeforeEach annotation. This method is invoked before each test method and the subclasses of this test class will inherit this setup method.
  3. Add a afterEach() method to our test class and annotate this method with the @AfterEach annotation. This method is invoked after each test method and this method is inherited.
  4. Add a static afterAll() method to our test class and annotate this method with @AfterAll annotation. This method is invoked once after all test methods have been run and the subclasses of this test class will inherit this teardown method.

After we have added all setup and teardown methods to our test class, its source code looks as follows:

 1import org.junit.jupiter.api.*;
 2
 3@DisplayName("JUnit 5 Example")
 4class JUnit5ExampleTest {
 5
 6    @BeforeAll
 7    static void beforeAll() {
 8        System.out.println("Before all test methods");
 9    }
10
11    @BeforeEach
12    void beforeEach() {
13        System.out.println("Before each test method");
14    }
15
16    @AfterEach
17    void afterEach() {
18        System.out.println("After each test method");
19    }
20
21    @AfterAll
22    static void afterAll() {
23        System.out.println("After all test methods");
24    }
25}

There are two things that I want to point out before we can continue this webinar:

  • We write information to System.out only because it’s the easiest way to demonstrate the invocation order of setup, teardown, and test methods.
  • The methods annotated with the @BeforeAll or @AfterAll don’t have to be static. This doesn’t belong to the scope of this webinar, but you can learn more about this by reading the JUnit 5 User Guide .

Additional Reading:

After we have added setup and teardown methods to our test class, we can finally write our first test methods. Let’s find out how we can do it.

Writing Our First Test Methods

A test method is a package private method that doesn’t return anything. Every test method must be annotated with the @Test annotation. Also, we can override the display name of a test method by annotating it with the @DisplayName annotation.

Let’s add two test methods to our test class:

  1. The firstTest() method has a custom display name and it writes a unique string to System.out.
  2. The secondTest() method has a custom display name and it writes a unique string to System.out.

After we have written these two test methods, the source code of our test class looks as follows:

 1import org.junit.jupiter.api.*;
 2
 3@DisplayName("JUnit 5 Example")
 4class JUnit5ExampleTest {
 5
 6    @BeforeAll
 7    static void beforeAll() {
 8        System.out.println("Before all test methods");
 9    }
10
11    @BeforeEach
12    void beforeEach() {
13        System.out.println("Before each test method");
14    }
15
16    @AfterEach
17    void afterEach() {
18        System.out.println("After each test method");
19    }
20
21    @AfterAll
22    static void afterAll() {
23        System.out.println("After all test methods");
24    }
25
26    @Test
27    @DisplayName("First test")
28    void firstTest() {
29        System.out.println("First test method");
30    }
31
32    @Test
33    @DisplayName("Second test")
34    void secondTest() {
35        System.out.println("Second test method");
36    }
37}

Again, I think that we should specify custom display names to our test methods because we can use real sentences instead of technical names.

Additional Reading:

We have just written our first test methods. Let’s see what happens when we run our unit tests.

Running Our Unit Tests

When we run our unit tests, we should see the following output:

1Before all test methods
2Before each test method
3First test method
4After each test method
5Before each test method
6Second test method
7After each test method
8After all test methods

This output proves that the setup, teardown, and test methods are run in the correct order. Let’s summarize what we learned from this webinar.

Summary

This webinar has taught us four things:

  • The junit-jupiter-api dependency allows us to write tests and extensions.
  • We have to ensure that the required test engines are in the classpath when we run our tests. If we use Maven, we should configure them when we declare the maven-surefire-plugin. If we use Gradle, we should add the required test engines to the testRuntime dependency configuration.
  • Junit 5 supports package private test classes, setup methods, teardown methods, and test methods.
  • We should override the display name of a test class and a test method by using the @DisplayName annotation.

Get the source code from Github