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>
Additional Reading:
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:
- Add the
junit-jupiter-api(version 5.0.0-M6) dependency to thetestCompiledependency configuration. This dependency provides the public API for writing tests and extensions. - Add the used test engine implementations to the
testRuntimeconfiguration. Because we want to run only tests that use JUnit 5, we have to add thejunit-jupiter-engine(version 5.0.0-M6) dependency to thetestRuntimedependency 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}
Additional Reading:
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:
- Create a new package private class.
- Annotate the class with the
@DisplayNameannotation and configure the display name of our test class. This is an optional step, but I recommend that we should do this because the@DisplayNameannotation 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.
Let’s add all setup and teardown methods to our test class. We can do this by following these steps:
- Add a
staticbeforeAll()method to our test class and annotate this method with@BeforeAllannotation. This method is invoked once before any test method is run and this method is inherited. - Add a
beforeEach()method to our test class and annotate this method with the@BeforeEachannotation. This method is invoked before each test method and the subclasses of this test class will inherit this setup method. - Add a
afterEach()method to our test class and annotate this method with the@AfterEachannotation. This method is invoked after each test method and this method is inherited. - Add a
staticafterAll()method to our test class and annotate this method with@AfterAllannotation. 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.outonly because it’s the easiest way to demonstrate the invocation order of setup, teardown, and test methods. - The methods annotated with the
@BeforeAllor@AfterAlldon’t have to bestatic. 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:
- The
firstTest()method has a custom display name and it writes a unique string toSystem.out. - The
secondTest()method has a custom display name and it writes a unique string toSystem.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-apidependency 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 thetestRuntimedependency 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
@DisplayNameannotation.