Migration from Junit 4 to Junit 5
Tags: java, junit, junit5, junit4
Published: 2018-04-23 07:25:29

---
title: Migration from Junit 4 to Junit 5
published: true
description: Migration from Junit 4 to Junit 5
tags: java,junit,junit5,junit4
cover_image: https://thepracticaldev.s3.amazonaws.com/i/6hdw81ckqycg9rta6fhj.jpg
---

While working on my current project, I got some time to migrate from [JUnit 4](https://junit.org/junit4/) to [JUnit 5](https://junit.org/junit5/).
Since JUnit 5 was released in September 2017, it's the right time to take a look at it.

My application is a java 8 maven project divided into 7 maven modules and each module has it owns integration and unit tests. However, one of these modules is dedicated to tests. It contains all the test needed dependencies and it's injected as scope test into others modules.
Our tests dependencies are the most common in a Java project. We use JUnit 4, [AssertJ](https://joel-costigliola.github.io/assertj/), [Mockito](http://site.mockito.org/), [DbUnit](http://dbunit.sourceforge.net/) and [Spring Test](https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html). 

At last, we also have a dedicated project to run end-to-end testings based on [Selenium](https://www.seleniumhq.org/), [Fluentlenium](http://fluentLenium.org/) and [JGiven](http://jgiven.org/).
Unfortunately, JGiven does not fully support JUnit 5. It's currently in an [experimental state](http://jgiven.org/userguide/#_junit_5_experimental), so I haven't started this migration.

## Dependencies

Let's start by adding the new JUnit dependencies :

```xml
<dependency>
   <groupId>org.junit.jupiter</groupId>
   <artifactId>junit-jupiter-engine</artifactId>
   <version>${junit.version}</version>
</dependency>
<dependency>
   <groupId>org.junit.vintage</groupId>
   <artifactId>junit-vintage-engine</artifactId>
   <version>${junit.version}</version>
</dependency>
<dependency>
   <groupId>org.junit.platform</groupId>
   <artifactId>junit-platform-launcher</artifactId>
   <version>${junit.platform.version}</version>
</dependency>
<dependency>
   <groupId>org.junit.platform</groupId>
   <artifactId>junit-platform-runner</artifactId>
   <version>${junit.platform.version}</version>
</dependency>

```

The important to take note of is the import of `junit-vintage-engine`. It provides the ability to run JUnit 4 tests and JUnit 5 tests simultaneously without difficulty.


## Unit Tests

The next step is to replace all imports of old JUnit annotations by the newest. 

```java
import org.junit.Test
```
become

```java
import org.junit.jupiter.api.Test;
```

Here's the mapping of each annotation:


| JUnit 4               | Junit 5                          |
|:---------------------:|:--------------------------------:|
| org.junit.Before      | org.junit.jupiter.api.BeforeEach |
| org.junit.After       | org.junit.jupiter.api.After      |
| org.junit.BeforeClass | org.junit.jupiter.api.BeforeAll  |
| org.junit.AfterClass  | org.junit.jupiter.api.AfterAll   |
| org.junit.Ignore      | org.junit.jupiter.api.Disabled   |


As we use AssertJ for all our assertions, I didn't need to migrate JUnit 4 assertions.

## Rules

One the biggest change is the removal of the concept of [rules](http://www.codeaffine.com/2012/09/24/junit-rules/), that has been replaced by [extension model](https://junit.org/junit5/docs/current/user-guide/#extensions). The purpose of extension is to extend the behavior of test classes or methods and it replaces JUnit runner and Junit Rules.

One rule that we all have used is `ExpectedException` and it can be easily replaced by JUnit `assertThrows` :

```java
   @Test
    void exceptionTesting() {
        Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
            throw new IllegalArgumentException("a message");
        });
        assertEquals("a message", exception.getMessage());
    }
```

Another well-known rule to migrate is `TemporaryFolder`. Unfortunately, JUnit 5 does not provide a replacement yet. There is an open [issue](http://github.com/junit-team/junit5/issues/1247) in Github. 

{%github https://github.com/junit-team/junit5/issues/1247 %}

So what can we do to make it work?

First of all, it's possible to keep tests using those rule in JUnit 4 thanks to `junit-vintage-engine`.


Another solution is to continue to use JUnit 4 `TemporaryFolder` rule by adding the dependency `junit-jupiter-migrationsupport`.

```xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-migrationsupport</artifactId>
    <version>${junit.version}</version>
</dependency>

```
This module enables to run JUnit 5 tests with rules. For example :

```java
@EnableRuleMigrationSupport
public class JUnit4TemporaryFolderTest {

    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    @Test
    public void test() throws IOException {
        temporaryFolder.newFile("new_file");
    }
}

```

However, this feature only supports :
* rules that extend `org.junit.rules.ExternalResource`
* rules that extend `org.junit.rules.Verifier`
* rule `ExpectedException`
and it's currently marked as [experimental](https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4-rule-support) so use it at your own risk.


Finally, one solution is to create our own [`TemporaryFolderExtension`]( https://gist.github.com/vgallet/8b52247c4cc57c6bc96923daff630fc1) based on Junit 4 implementation. 


```java
public class TemporaryFolderExtension implements BeforeEachCallback, AfterEachCallback {

   private final File parentFolder;
   private File folder;

   public TemporaryFolderExtension() {
       this(null);
   }

   public TemporaryFolderExtension(File parentFolder) {
       this.parentFolder = parentFolder;
   }

   @Override
   public void afterEach(ExtensionContext extensionContext) {
       if (folder != null) {
           recursiveDelete(folder);
       }
   }

   @Override
   public void beforeEach(ExtensionContext extensionContext) throws IOException {
       folder = File.createTempFile("junit", "", parentFolder);
       folder.delete();
       folder.mkdir();
   }

   public File newFile(String fileName) throws IOException {
       File file = new File(getRoot(), fileName);
       if (!file.createNewFile()) {
           throw new IOException("a file with the name \'" + fileName + "\' already exists in the test folder");
       }
       return file;
   }

   public File newFolder(String folderName) {
       File file = getRoot();
       file = new File(file, folderName);
       file.mkdir();
       return file;
   }

   private void recursiveDelete(File file) {
       File[] files = file.listFiles();
       if (files != null) {
           for (File each : files) {
               recursiveDelete(each);
           }
       }
       file.delete();
   }

   public File getRoot() {
       if (folder == null) {
           throw new IllegalStateException("the temporary folder has not yet been created");
       }
       return folder;
   }

}
```

This implementation does not fully support all extension features like [Parameter Resolution](https://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution) but at least, it allows us to fully migrate our tests to JUnit 5.
In addition, it's possible to inject extensions as rule by using [`@RegisterExtension`](https://junit.org/junit5/docs/current/user-guide/#extensions-registration-programmatic)

```java
@RegisterExtension
public TemporaryFolderExtension temporaryFolder = new TemporaryFolderExtension();

```

This annotation enables us to build an extension with parameters and to access is during test execution.


## Custom Rules

In my case, I had only one custom rule to migrate. Its goal is to create an in-memory SMTP server for asserting sending emails. 

```java
public class SMTPServerRule extends ExternalResource {

   private GreenMail smtpServer;
   private String hostname;
   private int port;

   public SMTPServerRule() {
       this(25);
   }

   public SMTPServerRule(int port) {
       this("localhost", port);
   }

   public SMTPServerRule(String hostname, int port) {
       this.hostname = hostname;
       this.port = port;
   }


   @Override
   protected void before() throws Throwable {
       super.before();

       smtpServer = new GreenMail(new ServerSetup(port, hostname, "smtp"));
       smtpServer.start();
   }

   public List<ExpectedMail> getMessages() {
       return Lists.newArrayList(smtpServer.getReceivedMessages()).stream()
           .parallel()
           .map(mimeMessage -> ExpectedMail.transformMimeMessage(mimeMessage)).collect(Collectors.toList());
   }

   @Override
   protected void after() {
       super.after();
       smtpServer.stop();
   }
}

```

To make it work as a JUnit extension, it only needs to implement `BeforeEachCallback` and `AfterEachCallback` interfaces instead of inheriting from `ExternalResource`. The main implementation is still the same.


```java
public class SMTPServerExtension implements BeforeEachCallback, AfterEachCallback {

   private GreenMail smtpServer;
   private String hostname;
   private int port;

   public SMTPServerExtension() {
       this(25);
   }

   public SMTPServerExtension(int port) {
       this("localhost", port);
   }

   public SMTPServerExtension(String hostname, int port) {
       this.hostname = hostname;
       this.port = port;
   }

   public List<ExpectedMail> getMessages() {
       return Lists.newArrayList(smtpServer.getReceivedMessages()).stream()
           .parallel()
           .map(mimeMessage -> ExpectedMail.transformMimeMessage(mimeMessage)).collect(Collectors.toList());
   }


   @Override
   public void afterEach(ExtensionContext extensionContext) throws Exception {
       smtpServer.stop();
   }

   @Override
   public void beforeEach(ExtensionContext extensionContext) throws Exception {
       smtpServer = new GreenMail(new ServerSetup(port, hostname, "smtp"));
       smtpServer.start();
   }

```


## Integration Tests

Next, I had to update Spring integration tests and it was quite easy as class `SpringExtension` is included in Spring 5.

```java
@RunWith(SpringJUnit4ClassRunner.class)
```
become

```java
@ExtendWith(SpringExtension.class)
```


## Mockito Tests

Let's continue with tests that use Mockito. Like we have done with Spring integration tests, we have to register an extension :

```java
@RunWith(MockitoJUnitRunner.class)
```
become

```java
@ExtendWith(MockitoExtension.class)
```

In fact, class `MockitoExtension` is not provided by Mockito yet and it will be introduced with Mockito 3.
One solution is the same as `TemporaryFolderExtension`...that is to keep our tests in JUnit 4. However, it's also possible to create our own extension and so Junit team give one implementation of [`MockitoExtension`](https://github.com/junit-team/junit5-samples/blob/r5.1.1/junit5-mockito-extension/src/main/java/com/example/mockito/MockitoExtension.java) in its samples.
I decided to import it into my project to complete my migration.


##Remove JUnit 4

Then, to ensure all my tests run under JUnit 5, I checked if there is any JUnit 4 dependency by executing :

```shell
mvn dependency:tree

```

And so, I had to exclude some of them :

```xml
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.dbunit</groupId>
            <artifactId>dbunit</artifactId>
            <version>${dbunit.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

```



## Maven

Last but not least, I needed to update the maven surefire plugin to make it works with JUnit 5.

```xml
<!--
        The Surefire Plugin is used during the test phase of the build lifecycle to execute the unit tests of an application.
        -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>${maven-surefire-plugin.version}</version>
            <dependencies>
                <dependency>
                    <groupId>org.junit.platform</groupId>
                    <artifactId>junit-platform-surefire-provider</artifactId>
                    <version>1.1.1</version>
                </dependency>
                <dependency>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-engine</artifactId>
                    <version>${junit.version}</version>
                </dependency>
            </dependencies>
        </plugin>

```

Be careful with the version of your maven surefire plugin as the `2.20` has a [memory leak](https://github.com/junit-team/junit5/issues/809). [JUnit documentation](https://junit.org/junit5/docs/current/user-guide/#running-tests-build-maven) suggests the version `2.21`.



## Conclusion

This migration was really easy, but even so, JUnit 5 is totally different from JUnit 4. In the end, I was able to remove the import of `junit-vintage-engine` as I don't have Junit 4 test anymore. I only regret the fact that I had to create my own temporary folder extension and Mockito extension.
Finally, it's possible to get more help with your migration by consulting [Junit5-samples](https://github.com/junit-team/junit5-samples).

A big thanks to [Sonyth](https://www.linkedin.com/in/sonyth-huber-57854424/), [Mickael](https://twitter.com/TsubaR00ck) and [Houssem](https://twitter.com/7ouss3m) for their time and proofreading.