DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
View Events Video Library
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Integrating PostgreSQL Databases with ANF: Join this workshop to learn how to create a PostgreSQL server using Instaclustr’s managed service

Mobile Database Essentials: Assess data needs, storage requirements, and more when leveraging databases for cloud and edge applications.

Monitoring and Observability for LLMs: Datadog and Google Cloud discuss how to achieve optimal AI model performance.

Automated Testing: The latest on architecture, TDD, and the benefits of AI and low-code tools.

Related

  • Creating Your Swiss Army Knife on Java Test Stack
  • I Don’t TDD: Pragmatic Testing With Java
  • Testcontainers With Kotlin and Spring Data R2DBC
  • Using Barcodes in iText 7

Trending

  • Send Your Logs to Loki
  • API Design
  • AWS Lambda vs. Fargate: The Battle of Cloud Giants
  • 6 Proven Kubernetes Deployment Best Practices for Your Projects
  1. DZone
  2. Coding
  3. Java
  4. Migrating From JUnit 4 to JUnit 5: A Step-By-Step Guide

Migrating From JUnit 4 to JUnit 5: A Step-By-Step Guide

Migrating from JUnit 4 to 5 can be a challenge if you’re not familiar with JUnit 5. Here’s a step-by-step guide that walks you through the process!

Kristof Horvath user avatar by
Kristof Horvath
·
Aug. 25, 23 · Tutorial
Like (2)
Save
Tweet
Share
7.14K Views

Join the DZone community and get the full member experience.

Join For Free

JUnit has been one of the most popular testing frameworks for a long time, with 85% of Java developers claiming to use it in JetBrains' 2021 survey. In addition, JUnit also plays a crucial role in the practice of TDD (test-driven development), a trending strategy for dev teams all over the world.

Developers use JUnit to implement unit testing, which in turn helps them improve code quality and accelerate programming. JUnit’s popularity is no surprise, considering that it contributes so much to enhanced productivity in delivering quality code. That said, some teams are still using JUnit 4 and are worried about the disruption that migrating to JUnit 5 could cause.

As the first major update since JUnit 4’s 2006 release, JUnit 5 dropped in 2017 with a range of new features. While version 5 of JUnit promises numerous benefits, migration is far from straightforward if you’re not familiar with all that’s changed. This migration guide from JUnit 4 to 5 aims to help your team reduce its technical debt by making the transition smoother.

In this article, we’ll cover the basic reasons to make the switch, as well as the steps you have to take to migrate from JUnit 4 to 5.

Why Migrate From JUnit 4 to 5?

JUnit 4 has certain limitations that become very apparent when compared to JUnit 5:

  • With JUnit 4, a single JAR library contains the framework, making it a little clunky to work with. Even if you’re only planning to use a single feature, you’ll need to import the entire library. In contrast, JUnit 5 offers more granularity that makes it easier to work with, especially when using the Maven and Gradle build systems.
  • JUnit 4 only allows you to execute tests with one test runner at a time, while JUnit 5 allows more extensions at a time, which lets you run multiple parallel tests. The Spring extension in JUnit 5 is easily combined with 3rd party or custom extensions.
  • With JUnit 5 supporting Java 8 features like lambda functions, you can create more powerful tests that are also simpler to maintain.
  • JUnit 4 lacks the useful features of JUnit 5 for describing, organizing, and executing tests. For example, to give a better overview, JUnit 5 lets you organize tests in hierarchies with better display names.

The great news is that you don’t have to migrate all at once to reap all those benefits since JUnit 4 and 5 can coexist. JUnit 4-specific artifacts are located in the old org.junit base package, while JUnit 5 provides new annotations and classes that are stored in the new org.junit.jupiter. There is no conflict, letting you take a gradual hybrid approach to migrating over to JUnit 5.

Step-By-Step Migration Guide From JUnit 4 to 5

Before we dive in, you should know there’s a fundamental architecture change from JUnit 4 to 5 that impacts how migration should be done. Specifically, while JUnit 4 consisted of a single module, JUnit 5 has three separate sub-projects or modules:

  • JUnit Jupiter: Brings a new programming model and extension model that includes new assertions, new annotations, Java 8 Lambda Expressions, etc.
  • JUnit Platform: Serves as the foundation for launching test frameworks on the JVM and defines the TestEngine API for the testing framework.
  • JUnit Vintage is dedicated to backwards compatibility by supporting the execution of legacy JUnit 3 and 4-based tests on the new JUnit 5 framework. This provides a gentle migration path for all developers to update to JUnit 5.

Overall, the migration process will take you through the following four key steps:

  1. Replacing dependencies
  2. Updating testing classes and methods with new annotations
  3. Replacing rules and runners with JUnit 5’s new extension model

Let’s dive right in!

1. Add JUnit 5 POM Dependencies, Remove JUnit 4 Dependency

Because of its monolithic architecture, the running of JUnit 4 tests is enabled by a single dependency in the Maven configuration. Upon migrating to JUnit 5, you’ll be replacing that with the JUnit Vintage dependency. As defined above, this is what will ensure backwards compatibility with JUnit 4 tests and enables JUnit 4 and 5 tests to coexist while you go ahead and complete the migration process.

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


Using JMock? Don’t forget that you’ll also need to upgrade the POM dependency to jmock-junit5:

<dependency>
  <groupId>org.jmock</groupId>
  <artifactId>jmock-junit5</artifactId>
  <version>${jmock.version}</version>
  <scope>test</scope>
</dependency>


2. Introduce JUnit 5 Jupiter Annotations, Assertions, and Assumptions

JUnit 5 uses different annotations than JUnit 4. In order to be able to use common annotations, you’ll need to use different imports — see our mapping of packages and a description of JUnit 5 annotations below. Overall, a key change is that test classes and methods don’t necessarily have to be public in JUnit 5.

As an example for new packages: in the case of @Test, instead of the old org.junit.Test; you’ll be using org.junit.jupiter.api.Test;

All core annotations reside in the org.junit.jupiter.api package (in the junit-jupiter-api module), while assertions are found in org.junit.jupiter.api.Assertions.

Annotations in JUnit 5

JUnit 5 brings an update to some annotations. The following table from HowToDoInJava sums this up perfectly:

Feature JUnit 4 JUnit 5
Declare a test method @Test @Test
Execute before all test methods in the current class @BeforeClass @BeforeAll
Execute after all test methods in the current class @AfterClass @AfterAll
Execute before each test method @Before @BeforeEach
Execute after each test method @After @AfterEach
Disable a test method/class @Ignore @Disabled
Test factory for dynamic tests NA @TestFactory
Nested tests NA @Nested
Tagging and filtering @Category @Tag
Register custom extensions NA @ExtendWith

Source

Let’s see a quick rundown of JUnit 5 annotations:

  • **@Test**: You’ll be using this one (with no attributes — that’s a change from JUnit 4, see more on this in a second) simply to declare a test method. Note that since attributes can no longer be used, the expected attribute isn’t available — instead, use the assertThrows() method. Similarly, the timeout attribute is replaced by the assertTimeout() method.
  • **@ParameterizedTest**: Used to denote that the method is a parameterized test.
  • **@RepeatedTest**: The method annotated with @RepeatedTest is to be used as a test template for repeated tests.
  • **@TestFactory**: Denotes that the method is a test factory for dynamic tests.
  • **@TestTemplate**: Used to denote that a method is a template for test cases to be repeated. The number of times it is invoked will be dependent on the variety of invocation contexts returned by the registered providers.
  • **@TestClassOrder**: Used for @Nested test classes, this annotation lets you configure the execution order for your test class.
  • **@TestMethodOrder**: You may know this one from JUnit 4 as @FixMethodOrder, used to configure the order of test method execution for your test class.
  • **@TestInstance**: Use it to configure the test instance lifecycle for your test class.
  • **@DisplayName**: To ensure an easy overview of your code, this annotation declares a custom display name for your test class or method. Note that such annotations are not inherited!
  • **@DisplayNameGeneration**: Use it to declare a custom display name generator for your test class.
  • **@BeforeEach**: Like JUnit 4’s @Before, methods annotated with @BeforeEach will be executed before each@Test, @RepeatedTest, @ParameterizedTest, or @TestFactory methods in your class.
  • **@AfterEach**: Similar to the previous one but used to denote that the method should be executed after each@Test, @RepeatedTest, @ParameterizedTest, or @TestFactory methods in your class. You used this one as @After in JUnit 4.
  • **@BeforeAll**: Replacing JUnit 4’s @BeforeClass annotation. As the name suggests, the method annotated with this one will be executed before all@Test, @RepeatedTest, @ParameterizedTest, or @TestFactory methods in your class.
  • **@AfterAll**: This one replaces JUnit 4’s @AfterClass annotation. You guessed it: methods annotated with @AfterAll will be executed after all@Test, @RepeatedTest, @ParameterizedTest, or @TestFactory methods in your class.
  • **@Nested**: Use this new annotation to annotate a class that is a non-static nested test class. Keep in mind that between Java versions 8 to 15, you can’t use @BeforeAll and @AfterAll methods in a @Nested test class — that is, unless you’re using a per-class test instance lifecycle. These annotations are not inherited.
  • **@Tag**: Tags are used for filtering tests both at the class and method levels. @Tags are replacing @Categories in JUnit 4 and are only inherited at the class level (not at the method level).
  • **@Disabled**: This one replaces JUnit 4’s @Ignore and is used to disable a test class or method. This annotation is not inherited.
  • **@Timeout**: Use this to fail a test, test factory, test template, or lifecycle method in case its execution takes longer than a defined duration.
  • **@ExtendWith**: You’ll be using this to register extensions programmatically via fields.
  • **@TempDir**: Use this to supply a temporary directory by injecting fields or parameters in a lifecycle method or a test method. You’ll find this one in the org.junit.jupiter.api.io package!

Assertions in JUnit 5

The most notable change regarding assertions is the order of parameters in the assertion functions. Specifically, all assert methods in JUnit 4 (located in org.junit.Assert) accept parameters for error messages as the first argument.

public static void assertEquals(long expected, long actual)
public static void assertEquals(String message, long expected, long actual)


Source

That changes in JUnit 5. Now, most assert() methods are found in org.junit.jupiter.Assertions and error messages are the last parameters. Assert methods in JUnit 5 have overloaded methods that support the display of parsing error messages upon test case failure. Here’s an example:

public static void assertEquals(long expected, long actual)
public static void assertEquals(long expected, long actual, String message)
public static void assertEquals(long expected, long actual, Supplier messageSupplier)


Source

Assumptions in JUnit 5

To extend support for lambda expressions and method references in Java 8, JUnit 5 (specifically, JUnit Jupiter) brings changes in assumptions (as static methods in the org.junit.jupiter.api.Assumptions class, instead of the org.junit.Assume class that you’re used to).

JUnit 4 (org.junit.Assume) offers the following methods for stating assumptions on the conditions of a test being considered meaningful:

  • assumeFalse()
  • assumeNoException()
  • assumeNotNull()
  • assumeThat()
  • assumeTrue()

That changes in JUnit 5, where org.junit.jupiter.api.Assumptions contains the methods for stating assumptions. The methods are also different:

  • assumeFalse()
  • assumingThat()
  • assumeTrue()

With assumeNoException and assumeNotNull removed, to migrate your tests, you’ll have to rewrite those assumptions using JUnit 5 methods, e.g. assumeFalse and assumeTrue. For instance, JUnit 4’s assumeNotNull(object) should be updated to assumeTrue(object != null).

3. Use JUnit 5’s New Rules and Runners

As briefly touched on before, the JUnit Jupiter extension model replaces the runners and rules extension points you’re used to in JUnit 4. From now on, you’ll be using the Extension API concept instead of rules.

Since @RunWith is no longer available, you’ll be using the @ExtendWith annotation and the org.junit.jupiter.api.extension package. That means that if you’ve used the Spring test runner in the past, you’ll have to replace that with the Spring extension.

For example, the Spring framework’s SpringJUnit4ClassRunner was used with @RunWith(SpringJUnit4ClassRunner.class) in JUnit 4. JUnit 5 replaces that with “extensions”, for which Spring provides the class SpringExtension, to be used with @ExtendWith(SpringExtension.class).

If you’re looking for a gradual migration path rather than switching at once, you’ll find it helpful that the junit-jupiter-migrationsupport module provides support for a subset of JUnit 4 rules and subclasses:

  • ExternalResource (including e.g. TemporaryFolder)
  • Verifier (including e.g. ErrorCollector)
  • ExpectedException

Using the class-level annotation @EnableRuleMigrationSupport in the org.junit.jupiter.migrationsupport.rules package enables you to avoid refactoring existing code lines that use the above rules.

Closing Thoughts

And that should be it! Now, you should have a better understanding of the differences between JUnit 4 and JUnit 5, and the above information should help you manage a smooth migration process. For a definitive guide with extended examples on GitHub, we recommend checking out Arho Huttunen’s excellent guide!

Have we missed any steps? Visit to give feedback so we can update this article!

Annotation JUnit Test method Java (programming language) Testing

Published at DZone with permission of Kristof Horvath. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Creating Your Swiss Army Knife on Java Test Stack
  • I Don’t TDD: Pragmatic Testing With Java
  • Testcontainers With Kotlin and Spring Data R2DBC
  • Using Barcodes in iText 7

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: