I. Overview
In this tutorial, we'll learn how to migrate from JUnit 4 to the latest JUnit 5 version and cover the differences between the two versions of the library.
For a general guide to using JUnit 5, see our article here.
Second, the advantages of JUnit 5
Let's start with the previous version, JUnit 4, which had some notable limitations:
- A single jar library contains the entire framework. We need to import the entire library, even if we only need a specific function. In JUnit 5 we get more granularity and can import only what is necessary.
- In JUnit 4 only one test runner can execute tests at a time (eg SpringJUnit4ClassRunner or Parameterized ). JUnit 5 allows multiple runners to work simultaneously.
- JUnit 4 never surpassed Java 7 and missed many features of Java 8. JUnit 5 takes full advantage of Java 8 features.
The idea behind JUnit 5 is to completely rewrite JUnit 4 to remove most of these shortcomings.
3. Differences
JUnit 4 is divided into the modules that make up JUnit 5:
- JUnit Platform -- This module covers all the extension frameworks we might be interested in: test execution, discovery, and reporting.
- JUnit Vintage -- This module allows backward compatibility with JUnit 4 and even JUnit 3.
3.1. Notes
JUnit 5 made important changes to its annotations. The most important point is that we can no longer use the @Test annotation to specify expectations.
expected parameter in JUnit 4:
@Test(expected = Exception.class) public void shouldRaiseAnException() throws Exception { // ... }
Now we can use the method assertThrows:
public void shouldRaiseAnException() throws Exception { Assertions.assertThrows(Exception.class, () -> { //... }); }
timeout property in JUnit 4:
@Test(timeout = 1) public void shouldFailBecauseTimeout() throws InterruptedException { Thread.sleep(10); }
Now the assertTimeout method in JUnit 5:
@Test public void shouldFailBecauseTimeout() throws InterruptedException { Assertions.assertTimeout(Duration.ofMillis(1), () -> Thread.sleep(10)); }
Here are some updates in JUnit 5:
- @Before annotation is now @BeforeEach
- @After annotation is now @AfterEach
- @BeforeClass annotation is now @BeforeAll
- @AfterClass annotation is now @AfterAll
- @Ignore annotation is now @Disabled
3.2. assertion
We can also write assertion messages in JUnit 5's lambda s to skip complex message construction:
@Test public void shouldFailBecauseTheNumbersAreNotEqual_lazyEvaluation() { Assertions.assertTrue( 2 == 3, () -> "Numbers " + 2 + " and " + 3 + " are not equal!"); }
Additionally, we can group assertions in JUnit 5:
@Test public void shouldAssertAllTheGroup() { List<Integer> list = Arrays.asList(1, 2, 4); Assertions.assertAll("List is not incremental", () -> Assertions.assertEquals(list.get(0).intValue(), 1), () -> Assertions.assertEquals(list.get(1).intValue(), 2), () -> Assertions.assertEquals(list.get(2).intValue(), 3)); }
3.3. Assumption
The new Assumptions class is now in org.junit.jupiter.api.Assumptions. JUnit 5 fully supports the existing assumption methods in JUnit 4, and also adds a new set of methods that allow us to run some assertions only in specific scenarios:
@Test public void whenEnvironmentIsWeb_thenUrlsShouldStartWithHttp() { assumingThat("WEB".equals(System.getenv("ENV")), () -> { assertTrue("http".startsWith(address)); }); }
3.4. Tag and filter
In JUnit 4, we can use the @Category annotation to group tests. In JUnit 5, the @Category annotation was replaced by the @Tag annotation:
@Tag("annotations") @Tag("junit5") public class AnnotationTestExampleTest { /*...*/ }
We can include/exclude specific tags using maven-surefire-plugin:
<build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <configuration> <properties> <includeTags>junit5</includeTags> </properties> </configuration> </plugin> </plugins> </build>
3.5. New annotations for running tests
In JUnit 4, we use @RunWithannotation to integrate test contexts with other frameworks, or to change the overall execution flow in test cases.
In JUnit 5, we can now use the @ExtendWith annotation to provide similar functionality.
For example, to use Spring features in JUnit 4:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration( {"/app-config.xml", "/test-data-access-config.xml"}) public class SpringExtensionTest { /*...*/ }
In JUnit 5 this is a simple extension:
@ExtendWith(SpringExtension.class) @ContextConfiguration( { "/app-config.xml", "/test-data-access-config.xml" }) public class SpringExtensionTest { /*...*/ }
3.6. New Test Rule Comments
In JUnit 4, we use @Rule and @ClassRule annotations to add special functionality to tests.
In JUnit 5, we can reproduce the same logic using the @ExtendWith annotation.
For example, let's say we have a custom rule in JUnit 4 that writes log traces before and after tests:
public class TraceUnitTestRule implements TestRule { @Override public Statement apply(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { // Before and after an evaluation tracing here ... } }; } }
We implement it in a test suite:
@Rule public TraceUnitTestRule traceRuleTests = new TraceUnitTestRule();
In JUnit 5, we can write the same code in a more intuitive way:
public class TraceUnitExtension implements AfterEachCallback, BeforeEachCallback { @Override public void beforeEach(TestExtensionContext context) throws Exception { // ... } @Override public void afterEach(TestExtensionContext context) throws Exception { // ... } }
Using JUnit 5's AfterEachCallback and BeforeEachCallback interfaces provided in the org.junit.jupiter.api.extension package, we can easily implement this rule in our test suite:
@ExtendWith(TraceUnitExtension.class) public class RuleExampleTest { @Test public void whenTracingTests() { /*...*/ } }
3.7. JUnit 5 Vintage
JUnit Vintage helps migrate JUnit tests by running JUnit 3 or JUnit 4 tests in the context of JUnit 5.
We can use it by importing JUnit Vintage Engine:
<dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <version>${junit5.vintage.version}</version> <scope>test</scope> </dependency>
4. Conclusion
JUnit 5 is a modular and modern version of the JUnit 4 framework. In this article, we describe the main differences between the two versions and show how to migrate from one version to the other.
A complete implementation of this article can be found at GitHub found in.