Migration from Junit 4 to Junit 5

While working on my current project, I got some time to migrate from JUnit 4 to JUnit 5.
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, Mockito, DbUnit and Spring Test.

At last, we also have a dedicated project to run end-to-end testings based on Selenium, Fluentlenium and JGiven.
Unfortunately, JGiven does not fully support JUnit 5. It’s currently in an experimental state, so I haven’t started this migration.

Dependencies

Let’s start by adding the new JUnit dependencies :

<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.

import org.junit.Test

become

import org.junit.jupiter.api.Test;

Here’s the mapping of each annotation:

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, that has been replaced by extension model. 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:

@Test
void exceptionTesting() {
Throwable exception = assertThrows(RuntimeException.class, () -> {
throw new RuntimeException("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 in Github.

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.

<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 :

@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 og.junit.rules.ExternalResource
  • rules that extend org.junit.rules.Verifier
  • rule ExpectedException

and it’s currently marked as experimental so use it at your own risk.

Finally, one solution is to create our own TemporaryFolderExtension based on Junit 4 implementation.

This implementation does not fully support all extension features like 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.

@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.

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.

Integration Tests

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

@RunWith(SpringJUnit4ClassRunner.class)

become

@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 :

@RunWith(MockitoJUnitRunner.class)

become

@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 MockitoExtensionin 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 :

mvn dependency:tree

And so, I had to exclude some of them :

<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.

<!--
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. JUnit documentation 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.

A big thanks to Sonyth, Mickael and Houssem for their time and proofreading.