Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

JUnit5 Assertion Migration Strategy

DZone's Guide to

JUnit5 Assertion Migration Strategy

Rather than migration your assertions line by line, create an abstraction class to represent the new implementation and then perform inline refactoring.

· Java Zone ·
Free Resource

Atomist automates your software deliver experience. It's how modern teams deliver modern software.

I’m experimenting with migrating my projects to JUnit 5.

Many of the “how to migrate to JUnit 5” blog posts show many differences, but not much strategy. I used a Branch-By-Abstraction strategy to migrate JUnit 4 assertions. This allowed me to experiment with using JUnit5 assertions or AssertJ assertions.

Difference Between JUnit 5 and JUnit 4

The main differences between JUnit4 and JUnit5 seem to pertain to annotations, rules, and assertions.

  • If I find and replace, then I can change the annotations.
  • For assertions, if they are statically imported, I could find and replace the static import statements.

But, assertions have the issue that in JUnit5, the messages have to go at the end of the parameter call list.

For example, JUnit 4 would be:

Assert.assertNotNull("name should not be null", name);


A direct conversion to JUnit5 would be similar. But that would be incorrect because this means that we assert that the String “name should not be null” is not null, and if it is, we show the error message in the variable name, which is the opposite from the current JUnit4 test.

Assertions.assertNotNull("name should not be null", name);


Assertions are the main issue I have to solve in my migration.

Useful Articles, but Not Viable Strategies

The following articles are all useful, but they do present a strategy for migrating. The strategy involves a lot of manual work to check the assertions.

None of these seem to present a strategy I like for migrating to JUnit 5.

Going line by line, while your code is ‘broken,’ it doesn’t seem like an effective approach.

Basic Steps

The first steps I took were:

Commenting out JUnit 4 in the pom.xml:

<!--
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
        -->


Next, we added JUnit 5 with the backward compatibility functionality in the vintage engine:

<dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.3.1</version>
        </dependency>
         <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <version>5.3.1</version>
        </dependency>


This provided a quick view of what doesn’t work. For more complicated projects, we might also need to add the migration support package.

You can find all the Migration tips on the JUnit 5 site:

Assertion Abstractions

Since abstractions are the main issue I face, I thought I might investigate the Assertion libraries. After all, if I’m going to have to change all the assertions, perhaps I should change them all to an Assertion library.

In fact, I might even migrate to AssertJ or Hamcrest first?

AssertJ

AssertJ uses an Assertions package and has a fluent interface so that it might be easier for beginners because of code completion.

AssertJ has ‘soft’ assertions to assert on all assertions and report on all even when one fails. This is a functionality in JUnit 5, as well, but I don’t need to use JUnit 5 Assertions to get it.

AssertJ has migration scripts that help it migrate. I would have to read these first to evaluate them.

Hamcrest

I have used Hamcrest before. But, I chose to stick to JUnit 4 because it was simpler and had more code completion.

Branch by Abstraction

Since I want to evaluate the different libraries, I could use a Branch-by-Abstraction approach.

Currently, my code looks like the following:

Assert.assertNotNull("name should not be null", name);


I only use a subset of “Assert” so that I could easily create an “Assert” class, which allows me to migrate to JUnit 5 without changing my code e.g.

package uk.co.compendiumdev.junitmigration.tojunit5;

import org.junit.jupiter.api.Assertions;

public class Assert {
    public static void assertNotNull(final String message, final String actual) {
    Assertions.assertNotNull(actual, message);
  }
}


If I had the above, then I could simply import that into my Test Class. There is no need to change the code in my Test Class, and then, I have used JUnit 5 Assertions.

If I used this throughout my code base, then instead of working on Assertion migration at a line-by-line level, I could use IntelliJ to perform an inline refactor for each method and it would change my JUnit4 code from:

Assert.assertNotNull("name should not be null", name);


And, it would change it to JUnit 5:

Assertions.assertNotNull(name, "name should not be null");


I haven’t seen this strategy mentioned in any of the migration blogs that I read.

Branch By Abstraction to AssertJ

Because this is a Branch-by-Abstraction migration, I could experiment with different implementations. I could create an AssertJ implementation of my Assert abstraction:

package uk.co.compendiumdev.junitmigration.toassertj;

import static org.assertj.core.api.Assertions.assertThat;

public class Assert {

    public static void assertNotNull(final String message, final String actual) {
        assertThat(actual).isNotNull().overridingErrorMessage(message);
    }
}


By changing the imports, I could switch between AssertJ or JUnit5 without changing my test code until I am happy to settle on one of the approaches.

I could clearly make this more flexible by coding to an interface and having the Assert wrapper as a configurable factory, which switches between the two, but I don’t need that degree of complexity.

Branch-By-Abstraction Strategy for Assertion Migration

This seems to be a simpler approach to assertion migration than I’ve seen mentioned.

This approach is open to me even if I statically import the methods because I can statically import the methods from my abstraction class instead of the main library.

I’m not sure why this isn’t the most communicated migration approach.

I created a video showing this approach in action below, and you can also check out all the code on GitHub:

I hope you enjoy the video!


Get the open source Atomist Software Delivery Machine and start automating your delivery right there on your own laptop, today!

Topics:
java ,junit 5 ,junit 4 ,migration ,strategy

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}