If you’ve ever written more than a few lines of Java code, odds are you’ve also written JUnit tests. JUnit is the testing framework most of us use.
I recently found myself tasked with migrating several hundreds of JUnit 4 tests to JUnit 5. Being a developer, I started looking for ways to automate this. To my surprise, I couldn’t easily find an existing tool to do this. In this post, I’ll show you what solution I came up with, and how to use it.
Please note that this post does not attempt to argue for or against migrating your suite of unittests to JUnit5, it assumes you’ve already made the choice to do so. It also assumes you use IntelliJ IDEA as development environment.
Under the hood, the solution this post describes uses a tool that is older than I am: Awk. If you’ve never heard of Awk before, do yourself a favor and read the excellent Awk in 20 minutes. If you know it, and you’re thinking “Is Awk the best tool for the job of modifying code?”, the answer I’d give is “Maybe not, but it’s simple, and it works well enough.”
With that out of the way, let’s dive into the solution I came up with!
Table of Contents
- Where can I find it?
- What does it do?
- How do I run this?
- Build the docker image
- Use the docker image
- Now that I’ve run it, what do I do?
- Add dependencies to your pom.xml
- In IntelliJ: format your code and optimize your imports
- In IntelliJ: fix whatever new lambda’s we’ve created
- Limitations
- Alternatives
Where can I find it?
The source is available on Gitlab: https://gitlab.com/techforce1/migrate-to-junit5.
What does it do?
- Migrate your imports to the new JUnit5 package names
@Before
and@After
become@BeforeEach
and@AfterEach
, respectively@BeforeClass
and@AfterClass
become@BeforeAll
and@AfterAll
, respectively@RunWith
in combination with either of the following runners is replaced by its JUnit5 counterpart:SpringRunner/SpringJUnit4ClassRunner
becomesSpringExtension
MockitoJUnitRunner
becomesMockitoExtension
@Ignore
becomes@Disabled
@Test
s using the feature where you declare what exception the code under test should throw by using the expected property is converted into a form that usesassertThrows()
.
Please be aware that the entirety of the test body is placed within the lambda parameter toassertThrows()
.@Test(expected = IllegalArgumentException.class) public void arrangementNumberNull() { builderValidFields().arrangementNumber(null).build(); }
gets turned into:
@Test public void arrangementNumberNull() { assertThrows(IllegalArgumentException.class, () -> builderValidFields().arrangementNumber(null).build()); }
How do I run this?
Build the docker image
From within the root of this repository:
docker build . -t migrate-to-junit5
Use the docker image
From within the root of your project’s repository:
docker run --rm -ti --mount type=bind,src=${PWD},dst=/src migrate-to-junit5
When it’s done, it’ll print a list of all files it modified.
Now that I’ve run it, what do I do?
Add dependencies to your pom.xml
You’re going to need to figure out on your own where you need to put what dependency, but here are the ones you’ll need most often:
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency>
Occasionally, when you use the MockitoJUnitRunner, you’ll also need to add the following dependency:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <scope>test</scope> </dependency>
NB: On all projects I’ve handled thus far, the parent POM already has dependency management for these dependencies, so you don’t need to explicitly version these dependencies.
In IntelliJ: format your code and optimize your imports
What’s probably the best way to do this, is through the use of the Version Control view:
Then hit whatever shortcut you have for formatting code (probably something like CTRL+ALT+L). You’ll be presented a dialog asking you what you want to do. Configure it like so:
In IntelliJ: fix whatever new lambda’s we’ve created
So, as part of the migration, any usage of @Test(expected = SomeException.class)
gets turned into a test that calls assertThrows(SomeException.class, () -> {/*test body goes here*/};)
. Sometimes, the original test consists of a single expression, which means the curly braces introduced by the migration aren’t necessary.
Thankfully, IntelliJ can fix that for us.
Hit whatever keyboard shortcut you have for Run Inspection by Name, which is CTRL+ALT+SHIFT+I for me (or use CTRL+SHIFT+A to open the action popup, and search for Run Inspection by Name). In the dialog that pops up, type Statement lambda can be replaced with expression lambda:
Then press Enter, and run the inspection on all Uncommitted files:
Finally, in the Inspection results view, if the inspection found anything, select everything (CTRL+A) and click the button that says Replace with expression lambda:
Limitations
- There’s no support for
@Rules
. If your tests depend on any@Rule
, you’re on your own. - The script is rather naive in the way it adds imports: it relies on your IDE to remove unused ones. This can lead to the unfortunate situation where your code doesn’t compile because of unused imports (mostly the Mockito extension stuff).
- The following cases may confuse the script and lead to incorrect conversion of tests that use the
@Test(expected=...)
feature:- Curly braces at the end of lines that aren’t syntactically relevant (such as in comments)
- Multiple curly braces on a single line
Alternatives
Since creating this solution, I’ve become aware of a few alternative solutions. Here’s a summary of the most interesting ones:
- Refactoring from within IntelliJ: much like this solution, it doesn’t pretend to be a perfect solution. It doesn’t appear to try to migrate any
@RunWith
annotations, but it does understand Java’s syntax, so it’s unlikely to produce uncompilable code. - Semantic code search and transformation with Rewrite: Rewrite looks like a nice tool to write automated refactorings of (among others) Java code. The authors have written the rewrite-testing-frameworks module. I haven’t looked very hard into this, but I like the concept, and might reach for this first next time I run into a situation where I want to automate some refactoring.
That’s it. Enjoy!