7 reasons why you should start using JUnit 5 today

“He who moves not forward, goes backward.” ― Johann Wolfgang von Goethe

The latest version of JUnit is just around the corner. According to the official roadmap the final release is planned for Q3 2017. At the moment of writing, 4 milestones have been published and a few more are planned before release candidates, but it doesn’t mean you have to wait several months to make use of Junit 5. The goal of this post is to encourage you in seven points to start playing with the new version the framework right away.

Advertisement

1. Ready to use

It’s not unusual, when a new version of language, application server, or library comes to light, developers tend to postpone learning to a time when the industry is ready to adopt the changes. Just look at JavaScript. Two years after the release of the ES2015 specification web browsers are still struggling to support all language features. Transpilers did a great job to help with popularization but as a side effect introduced an additional step in the development process.

The JUnit 5 team facilitates the adoption of the new version from the beginning. IntelliJ IDEA already runs tests natively within the IDE. Eclipse introduced its beta support as well. Two most popular build tools, which are Maven and Gradle, handle JUnit 5 tests without an issue. Moreover, if you use some less popular tool, a console launcher will help you to execute tests automatically. Last but not least, there is also a dedicated JUnit 4 Runner which allows running new tests in environments which support only the previous version. As you can see, JUnit 5 gives plenty of choices.

2. Easy to learn

At the first glance, the new API (also known as JUnit Jupiter) strongly resembles its predecessor. The level of similarity is so high that you may even think that creating a new major release of the framework for such cosmetic changes is a considerable abuse. The most popular assertions are still the same. The only differences are in the package where they are placed and the order of parameters in overloaded variants. Widely known annotations changed their names, but are still responsible for the same actions. If you are familiar with JUnit 4 you can grasp the basics of JUnit 5 within a few minutes.

At this point, you may ask, if it is so similar to the previous version then why you should bother. In short, JUnit 5 is like a superset of the predecessor and has much more to offer.

3. Brand new features

Once you familiarize yourself with the basics (approx. 15 minutes :)) you can move to the next level. Depending on your needs, you should consider experimenting with:

  • new assertions
  • nested test classes – interesting not only for Behavior Driven Development followers
  • dynamic tests – generating test cases at runtime
  • test extensions –allowing you to redefine the behavior of tests and test classes in a reusable and pluggable way

4. Solved problems of predecessor

Nothing is perfect and so does JUnit 4. There are several known different size issues which have been worked around by the community. Let’s quickly look over these problems.

4.1. Exception verification

The flagship example of all aforementioned issues is exception checking. Consider the following JUnit 4 test:

@Test(expected = IllegalArgumentException.class)
public void shouldThrowException() throws Exception {
    Task task = buildTask();
    LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
    task.execute(oneHourAgo);
}

Imagine we want to test if the method execute() of the task object throws the IllegalArgumentException if the given argument is in the past. The test looks fine, but what happens if the exception is thrown by the buildTask() method? The test will give us a false positive. We should check if the exception occurs exactly in the place where it is expected, not the whole test body. JUnit 5 gives you assertThrows() out-of-the-box, which gracefully handles the described problem as in the following snippet:

@Test
void shouldThrowException() throws Exception {
    Task task = buildTask();
    LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
    assertThrows(IllegalArgumentException.class,
                 () -> task.execute(oneHourAgo));
}

4.2. Timeout measurement

In addition, a similar set of assertions has been provided to verify timeouts in the code provided within a lambda expression. You don’t have to worry that the setup part of a test will have an impact on the measured execution time. Now you can choose which code is measured. Also, there is an option to stop execution when the timeout is exceeded or to continue and measure the actual time required to execute tested code.

@Test
void shouldTimeout() throws Exception {
    ExpensiveService service = setupService();
    assertTimeout(ofSeconds(3), () -> {
        service.expensiveMethod();
    });
}

4.3. Parameterized tests

If you don’t like the fact that JUnit4 requires using test class fields for parameterized tests, the new approach proposed in JUnit 5 will surely meet your needs. Parameters are now connected directly to a test method and nothing prevents you from putting different parameterized tests in a single class, what isn’t possible in JUnit 4.

Test parameters can be supplied by an array of sources including CSV-like strings, external CSV files, enums, factory methods, and dedicated provider classes. String literals are automatically converted into numerous types and you can also provide your custom converters if the built-in ones don’t cover your use cases.

4.4. Multiple runners

Probably the greatest issue of JUnit 4 is its inability to use several test runners in a single class. Limitations of parameterized tests or nested test classes can be solved with runners, but they cannot be used with others. We are forced to pick just one. For instance, before Spring 4.2 every test depended on your application context had to use the SpringJUnit4ClassRunner. Now you can use a dedicated rule to work around the issue so you can use a different runner, but still only one. JUnit 4 wasn’t designed with extensibility in mind and it pays the price of backward compatibility. The JUnit 5 team has chosen a different path and planned the architecture of the new version to solve this problem thanks to already mentioned extensions.

5. Simple migration

Are you already having hundreds of tests in your current project and at the very thought of rewriting you get shivers? Relax. The JUnit 5 team thought about different options for a migration. First things first, JUnit 4 and 5 can both be used in one project without conflicts. Once you add the new version to project’s dependencies, you have two alternatives for running tests together.

The least invasive option is to write new tests with JUnit 5 API and run them with a dedicated JUnitPlatform runner for JUnit 4. But wait, didn’t we just say that runners are the crux of the matter? Well, the runner has its limitations and supports only a subset of features introduced by the new version. Yet, it’s good enough to start using the latest API in a living project. The fastest way to learn is to practice on a real problem, right?

The second alternative is to promote JUnit 5 as a master runner of all tests. Besides all the best features of the framework, you will get the ability to execute old tests within the new platform, but with one little reservation. Because of conceptual differences, only selected rules from JUnit 4 are supported in JUnit 5. Before you take a leap of faith into JUnit 5 platform, spend some time to familiarize yourself with the limitations.

6. Opportunity to contribute

Since the new version is still under development, there is no better moment to request a feature that might be useful. It is you, the user of the framework, who knows best what is expected from a dependency you put into your project. Every reasonable idea may be reported in the issue tracker of the JUnit 5 project.

But before you open a new ticket, make sure that the framework doesn’t already have what you are looking for. Don’t waste the time of the team for issues that have already been solved or don’t add much value. However, if you have a good proposal, don’t hesitate and write a descriptive overview. No one said that you can contribute to open source only by writing code. Ideas also matters. All issues can be found here.

7. Future of JVM testing

JUnit 5 isn’t just another pretender to the testing framework throne. The architecture of JUnit 5 has been greatly altered not only because of the breaking changes in the public API and the introduction of the extension model. One of the goals was to create a base platform for JVM testing frameworks. What does it mean? Let’s take a glance at the following diagram.

As you can see, just like onions and ogres, JUnit 5 architecture has layers. The core of the framework is the platform. IDEs and build tools as clients communicate with the platform in order to execute tests in a project. Available TestEngine implementations called by the platform discover and run tests and the unified output is reported through the platform back to clients.

The key point is extensibility, but not at the test class level as mentioned before. It is extensibility at the level of the whole testing platform. Any framework can run its tests on the JUnit platform as long as it provides an implementation of the TestEngine interface. With a little effort, by covering this single integration point, that imaginary framework will get the support of all IDEs and build tools which are already integrated with the platform. The barriers to enter the industry for newly invented frameworks will be much lower, because they won’t have to provide support for aforementioned IDEs and tools.

But what do all these changes give you as a developer? It should be much easier to convince your manager, lead developer, or whoever blocked your last suggestion to experiment with an exotic testing framework once that framework has integration with all major tools on the JVM development market. JUnit Vintage, which executes old JUnit 4 tests, is a prime example this is a proven concept.

Not convinced yet?

Even if you are not an early adopter type of developer, you should still keep an eye on the changes introduced in the newest version of JUnit. With all the advantages that JUnit 5 brings and the fame of the predecessor, its way into mainstream is just a matter of time. For those who don’t want to experiment on their real applications, a side project might be a great option to get to know with the new JUnit Jupiter API. If you find any issue or have a remark, feel free to report it. Open source is created by the whole community, not only by the core project development team. It is up to you whether you want to become a contributor.

Facebooktwittergoogle_plusredditlinkedinmail
Advertisement