In this article, we will take a look at the steps required for migrating from JUnit 4 to JUnit 5. We will see how to run existing tests along with the new version, and what changes we have to do to migrate the code.
This post is part of the JUnit 5 Tutorial.
JUnit 5 Advantages
JUnit 5 has been designed to be modular unlike the previous versions. The key point of the new architecture is to separate concerns between writing tests, extensions and tools.
JUnit has been split into three different sub-projects:
- The basis, JUnit Platform provides build plugins and an API for writing test engines
- JUnit Jupiter is the new API for writing tests and extensions in JUnit 5
- Finally, JUnit Vintage allows us to run JUnit 4 tests with JUnit 5
One of the biggest flaws of JUnit 4 is that it does not support multiple runners (so you cannot use e.g.
Parameterized at the same time). In JUnit 5 this is finally possible by registering multiple extensions.
Furthermore, JUnit 5 utilizes Java 8 features like lambdas for lazy evaluation. JUnit 4 never advanced beyond Java 7 missing out on Java 8 features.
Also, JUnit 4 has shortcomings in parameterized tests and lacks nested tests. This has inspired third-party developers to create specialized runners for these situations. JUnit 5 adds better support for parameterized tests and native support for nested tests along with some other new features.
Let's see what we need to do to run existing test on the new platform. In order to run both JUnit 4 and JUnit 5 tests we need:
- Jupiter test engine to run JUnit 5 tests
- Vintage test engine to run JUnit 4 tests
- Jupiter API to write JUnit 5 tests
In addition to this, to run the tests with Maven we also need the Surefire provider from JUnit Platform. We have to add all the dependencies to pom.xml:
Likewise, to run the tests with Gradle we also need the Gradle plugin from JUnit Platform. Again, we have to add all the dependencies to build.gradle:
Migration of existing tests mostly involves finding and replacing some package and class names. Let's see what changes there are.
Annotations reside in the org.junit.jupiter.api package instead of org.junit package.
Some of the annotations are also different:
In most cases, we can just find and replace the package and class names.
However, we can not use
timeout attributes with the
@Test annotation anymore.
expected attribute in JUnit 4 can be replaced with the
assertThrows() method in Junit 5:
timeout attribute can be replaced with the
We can also see that neither test classes nor test methods need to be
public in JUnit 5. We might actually get a IDE warning that they can be made package-private.
Methods for asserting reside in the org.junit.jupiter.api.Assertions class instead of org.junit.Assert class.
In most cases, we can just find and replace the package names.
However, if we have provided the assertion with a custom message we will get compiler errors. The optional assertion message is now the last parameter. This order of parameters feels more natural:
It is also possible to lazily evaluate assertion messages like in the example. This avoids constructing complex messages unnecessarily.
There is also another issue when asserting
String objects with a custom assertion message. The order of the parameters is different but we won't get a compiler error because all the parameters are
String type. We can easily spot these cases because the tests will fail when we run them.
Assumption methods reside in org.junit.jupiter.Assumptions class instead of org.junit.Assume class.
These methods have been changed in a similar way to assertions. The assumption message is now the last parameter:
@Category annotation from JUnit 4 has been replaced with a
@Tag annotation in JUnit 5. Also, we no longer use marker interfaces but instead pass the annotation a string parameter.
In JUnit 4 we use categories whereas in JUnit 5 we use tags:
We can configure filtering of tests by tags in Maven pom.xml:
Correspondingly, we can configure filtering in Gradle build.gradle:
@RunWith annotation from JUnit 4 does not exist in JUnit 5. We can implement the same functionality by using the new extension model in the org.junit.jupiter.api.extension package and the
For example, we might be using the Spring Test runner in JUnit 4. We have to replace the runner with a Spring extension in JUnit 5. If we are using Spring 5 the extension comes bundled with Spring Test:
However, if we are using Spring 4 it does not come bundled with
SpringExtension. We can still use it but it requires an extra dependency from the JitPack repository.
SpringExtension with Spring 4 we have to add the dependency in Maven pom.xml:
Same way, we have to add the dependency in build.gradle when using Gradle:
@ClassRule annotations from JUnit 4 do not exist in JUnit 5. We can implement the same functionality by using the new extension model in the org.junit.jupiter.api.extension package and the
However, to provide a gradual migration path there is support for a subset of JUnit 4 rules and their subclasses in junit-jupiter-migrationsupport module:
Existing code using these rules can be left unchanged by using the class level annotation
@EnableRuleMigrationSupport in the org.junit.jupiter.migrationsupport.rules package.
To enable the support in Maven we have to add the dependency in pom.xml:
To enable the support in Gradle we have to add the dependency in build.gradle:
Migrating custom JUnit 4 rules requires re-writing the code as a JUnit 5 extension.
The rule logic applied as a
@Rule can be reproduced by implementing the
Respectively, we can reproduce rule logic applied as a
@ClassRule by implementing the
For example, if we have a JUnit 4 rule that does performance logging:
In turn, we can write the same rule as a JUnit 5 extension:
Migrating from JUnit 4 to JUnit 5 requires some work depending on how the existing tests have been written.
- We can run JUnit 4 tests along with the JUnit 5 tests to allow for gradual migration.
- In a lot of cases, we only have to find and replace package and class names.
- We might have to convert custom runners and rules to extensions.
The example code for this guide can be found on GitHub.