Still using JUnit4?

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.

Tags: Back-end

Posted by fenderville on Sat, 08 Oct 2022 21:31:22 +1030