Migrating from JUnit 4 to JUnit 5

1. Overview

In this article, we’ll see how we can migrate from JUnit 4 to the latest JUnit 5 release – with an overview of the differences between the two versions of the library.

For the general guidelines on using JUnit 5, see our article here.

2. JUnit 5 Advantages

Let’s start with the previous version – JUnit 4 has some clear limitations:

  • The entire framework was contained in a single jar library. The whole library needs to be imported even when only a particular feature is required. In JUnit 5, we get more granularity and can import only what is necessary
  • One test runner can only execute tests in JUnit 4 at a time (e.g. SpringJUnit4ClassRunner or Parameterized ). JUnit 5 allows multiple runners to work simultaneously
  • JUnit 4 never advanced beyond Java 7, missing out on a lot of features from Java 8. JUnit 5 makes good use of Java 8 features

The idea behind JUnit 5 was to completely rewrite JUnit 4 to solve most of these drawbacks.

3. Differences

JUnit 4 was divided into modules that comprise JUnit 5:

  • JUnit Platform – this module scopes all the extension frameworks we might be interested in test execution, discovery, and reporting
  • JUnit Vintage – this module allows backward compatibility with JUnit 4 or even JUnit 3

3.1. Annotations

JUnit 5 comes with important changes within its annotations. The most important one is that we can no longer use @Test annotation for specifying expectations.

The expected parameter in JUnit 4:

@Test(expected = Exception.class)
public void shouldRaiseAnException() throws Exception {
    // ...
}

Now, we can use a method assertThrows:

public void shouldRaiseAnException() throws Exception {
    Assertions.assertThrows(Exception.class, () -> {
        //...
    });
}

The timeout attribute 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));
}

Other annotations that were changed within JUnit 5:

  • @Before annotation is renamed to @BeforeEach
  • @After annotation is renamed to @AfterEach
  • @BeforeClass annotation is renamed to @BeforeAll
  • @AfterClass annotation is renamed to @AfterAll
  • @Ignore annotation is renamed to @Disabled

3.2. Assertions

We can now write assertion messages in a lambda in JUnit 5, allowing the lazy evaluation to skip complex message construction until needed:

@Test
public void shouldFailBecauseTheNumbersAreNotEqual_lazyEvaluation() {
    Assertions.assertTrue(
      2 == 3, 
      () -> "Numbers " + 2 + " and " + 3 + " are not equal!");
}

We can also 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. Assumptions

The new Assumptions class is now in org.junit.jupiter.api.Assumptions. JUnit 5 fully supports the existing assumptions methods in JUnit 4 and also adds a set of new methods to allow running some assertions only under specific scenarios only:

@Test
public void whenEnvironmentIsWeb_thenUrlsShouldStartWithHttp() {
    assumingThat("WEB".equals(System.getenv("ENV")),
      () -> {
          assertTrue("http".startsWith(address));
      });
}

3.4. Tagging and Filtering

In JUnit 4 we could group tests by using the @Category annotation. With JUnit 5, the @Category annotation gets replaced with the @Tag annotation:

@Tag("annotations")
@Tag("junit5")
@RunWith(JUnitPlatform.class)
public class AnnotationTestExampleTest {
    /*...*/
}

We can include/exclude particular tags using the 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

The @RunWith was used to integrate the test context with other frameworks or to change the overall execution flow in the test cases in JUnit 4.

With JUnit 5, we can now use the @ExtendWith annotation to provide similar functionality.

As an example, to use the Spring features in JUnit 4:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  {"/app-config.xml", "/test-data-access-config.xml"})
public class SpringExtensionTest {
    /*...*/
}

Now, in JUnit 5 it is a simple extension:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(
  { "/app-config.xml", "/test-data-access-config.xml" })
public class SpringExtensionTest {
    /*...*/
}

3.6. New Test Rules Annotations

In JUnit 4, the @Rule and @ClassRule annotations were used to add special functionality to tests.

In JUnit 5. we can reproduce the same logic using the @ExtendWith annotation.

For example, say we have a custom rule in JUnit 4 to write log traces before and after a test:

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

And we implement it in a test suite:

@Rule
public TraceUnitTestRule traceRuleTests = new TraceUnitTestRule();

In JUnit 5, we can write the same in a much more intuitive manner:

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 available in the package org.junit.jupiter.api.extension, we easily implement this rule in the test suite:

@RunWith(JUnitPlatform.class)
@ExtendWith(TraceUnitExtension.class)
public class RuleExampleTest {
 
    @Test
    public void whenTracingTests() {
        /*...*/
    }
}

3.7. JUnit 5 Vintage

JUnit Vintage aids in the migration of JUnit tests by running JUnit 3 or JUnit 4 tests within the JUnit 5 context.

We can use it by importing the 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

As we’ve seen in this article, JUnit 5 is a modular and modern take on the JUnit 4 framework. We have introduced the major differences between these two versions and hinted how to migrate from one to another.

The full implementation of this tutorial can be found in over on GitHub.

Related posts:

Java Program to Implement Leftist Heap
Guide to WeakHashMap in Java
Serverless Functions with Spring Cloud Function
Java Program to Implement Dijkstra’s Algorithm using Set
Quick Guide to Spring Controllers
Java Program to Check Whether a Given Point is in a Given Polygon
Java Program to Implement Heap Sort Using Library Functions
Java Program to Implement Find all Back Edges in a Graph
Login For a Spring Web App – Error Handling and Localization
Java Program to Find Minimum Number of Edges to Cut to make the Graph Disconnected
Spring WebClient Filters
Sử dụng Fork/Join Framework với ForkJoinPool trong Java
Converting a List to String in Java
@DynamicUpdate with Spring Data JPA
Java Program to Check Cycle in a Graph using Graph traversal
Mapping a Dynamic JSON Object with Jackson
Spring Boot - Servlet Filter
HttpAsyncClient Tutorial
Tạo ứng dụng Java RESTful Client với thư viện OkHttp
Spring Boot - Securing Web Applications
Java Program to Check if an UnDirected Graph is a Tree or Not Using DFS
Java Program to Implement Stack using Two Queues
Java Program to Test Using DFS Whether a Directed Graph is Weakly Connected or Not
List Interface trong Java
Java Program to Generate Random Numbers Using Multiply with Carry Method
Checking for Empty or Blank Strings in Java
Spring Boot - Enabling HTTPS
Java Program to Find the Peak Element of an Array O(n) time (Naive Method)
Spring Data JPA @Query
Implementing a Runnable vs Extending a Thread
Hướng dẫn sử dụng Java String, StringBuffer và StringBuilder
Java Program to Find Transpose of a Graph Matrix