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

Porting Your Existing Tests to JUnit5

DZone's Guide to

Porting Your Existing Tests to JUnit5

Learn the changes you need to make in your code to migrate your test base from JUnit 4 to JUnit 5, and what new features make the move worth it.

· DevOps Zone
Free Resource

“Automated Testing: The Glue That Holds DevOps Together” to learn about the key role automated testing plays in a DevOps workflow, brought to you in partnership with Sauce Labs.

On the 10th of September JUnit5 finally reached the General Availability stage.

If you don’t know what I am talking about, you can look at the JUnit 5 User Guide or a Gentle introduction to JUnit5.

There are several tutorials on the new cool features of JUnit5, previously known as Project Jupiter, but I couldn’t find any experience report on migrating a medium-sized test base (a little more than 200 tests) from JUnit4.

TL;DR: it’s pretty painless, but requires some trivial changes to all your tests.

Slightly Longer Version

The first step is to update Maven or Gradle dependencies. Here are my new Gradle ones:

testCompile group: ‘org.junit.jupiter’, name: ‘junit-jupiter-api’, version: ‘5.0.0’
testRuntime(“org.junit.jupiter:junit-jupiter-engine:5.0.0”)
testRuntime(“org.junit.platform:junit-platform-launcher:1.0.0”)

The last one could be unnecessary, depending on your IDE version.

Now, there are two possible courses of action: either we switch one test at a time, or we switch them all together and then use find/replace to fix the compilation errors.

Since I don’t particularly enjoy fixing code for hours, I chose the latter. I removed all the JUnit4 dependencies from Gradle and looked at the many compilation errors.

1) Imports

First I had to fix the imports, so I replaced all

import org.junit.Test;

with

import org.junit.jupiter.api.*;

and all

import static org.junit.Assert.*;

with

import static org.junit.jupiter.api.Assertions.*;

With the same system, I also removed all the rest of the JUnit4 imports, like Ignore, Before, etc.

A Little Digression About Assertj

I used this opportunity to also add AssertJ (Fluent assertions for Java); you may consider it, as well. In that case, you also need to add:

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

Here are some examples of rewritten assertions with AssertJ:

Before:

assertTrue( elapsed < 5);
assertTrue( mystring.contains("ABC"));

After:

assertThat( elapsed).isLessThan(5);
assertThat( mystring).contains("ABC");

2) @before and @after

The JUnit4 Before assertion is split in JUnit5 into BeforeAll and BeforeEach. Those are exactly like the Before and BeforeClass annotations of JUnit4.

BeforeAll is run only once per class; BeforeEach is run before each test. What’s more, the BeforeAll must be a static method.

What I did is replace all @Before with @BeforeEach and then created a method annotated with @BeforeAll where necessary (just a few places).

I didn’t use the After annotation, but it would be same thing, with @AfterAll and @AfterEach.

Maybe I’m just nostalgic, but I liked it better when there were only the setUp and tearDown methods without need of any annotation. Are there really people out there who are so keen to use different method names?

3) Asserts

If you statically imported the Assert class in JUnit4, your asserts should work directly in JUnit5, apart from a problem with messages. If you had Assert.assertxxx calls, you can just remove the “Assert.” part.

All the asserts of JUnit4 are also present in JUnit5, at least as far as I can tell. Instead, there is a problem with messages in asserts: in JUnit4, each assert can have an assertion message that the TestRunner would use if the assertion fails. It is the same in JUnit5, but whilst in JUnit4 and previous, this was the first parameter, in JUnit5, it is consistently the last one.

I suppose the change is because auto-completion on IDE was easily tricked by having a string in the first place if you were comparing strings. Or maybe not, but whatever the reason, now you have to switch the parameters to move to JUnit5.

JUnit4:

assertNotNull(“User is not present”, user );

JUnit5:

assertNotNull(user, “User is not present”);

It’s a bit annoying, but the cool thing I have learned is that if you have a recent version of IntelliJ, you can use the cool key combination Ctrl+Alt+Shift <arrow left or arrow right> to switch quickly parameters inside functions.

4) Expected Exceptions

One of the things I didn’t like in JUnit was the suggested way to test for expected exceptions. I found this annotation ugly and not very precise.

For example, I cannot test the exact line where I expect the exception to be thrown and its message.

@test(expected=exception.class)

In my test base, this was not used, and instead, I used a try…catch when I needed to test exceptions.

With JUnit5, the situation is much improved with the new assertThrows.

Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
 throw new IllegalArgumentException("a message");
});

5) @ignore

This is very easy: replace all your @Ignore annotations with @Disabled.

To be honest, I don’t really understand what could be the reason here; it seems a bit gratuitous to me.

6) @runwith

JUnit had a quite limited way to run tests in a special context, like Mockito or Spring. You need to use the annotation RunWith, but you cannot have more than one per class, so it was complicated to use your favorite mocking framework and Spring at the same time.

With JUnit5, you have to use the ExtendWith annotation, and you can add several of them to the same class.

Since there is a module using SpringBoot in my project (unfortunately), I had to replace:

@RunWith(SpringRunner.class)

with

@ExtendWith(SpringExtension.class)

Conclusions

After all these changes, at first compile, everything went well and I haven’t found any other problems. All in all, it took me about one hour to do the migration.

Now the final question is: “Why bother?” Why not continue with JUnit4?

Well, there are a lot of nice features in JUnit5, like better modularity, better categories, better concurrent testing, etc. but the real killer feature for me is the assertAll.

(

Or even checking that a map contains all the keys we need, each with a valid value in one go:

assertAll(“keys”, keys.stream().map(k -> (Executable) () -> {
   assertNotNull(myMap.get(k), “Null for key “ + k);
   assertFalse(myMap.get(k).isEmpty());
}));

Previously, the first failed assertion would prevent the others to run, instead now is possible to group some assertions and let them run all and having a full report of which failed and which passed.

I already started to improve my tests with assertJ and assertAll, without doing a big refactoring, now everytime I have to touch a test I will improve it with the new features.

I wish you a lot of fun with JUnit5!

Learn about the importance of automated testing as part of a healthy DevOps practice, brought to you in partnership with Sauce Labs.

Topics:
junit 5 ,tdd ,intellij ,test automation ,devops

Published at DZone with permission of Uberto Barbini, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}