Site iconJava PDF Blog

What’s new in JUnit 5?

This month our posts are focusing on code quality and we have already covered how you can use IDEs to improve your code to higher standard. Building on that this post will be about how you can maintain your current code quality level, and the easiest way to do that is to properly test it.

There are a few different Java testing frameworks out there to support and simplify the testing process. I’m going to write about JUnit 5 today because we plan to use it and its cool new features in the future. I’ve picked out 6 JUnit 5 features that I think will make writing our tests easier and more efficient.

What is JUnit 5?

JUnit 5 is the most recent major version of the JUnit developer-side testing framework. It is an open-source project that allows unit tests to be carried out. Unlike previous JUnit versions though, JUnit 5 is composed of modules from 3 different sub projects:

JUnit Platform
– Allows multiple test engines to be run simultaneously and defines Launcher APIs which are used by both IDEs and build tools to launch the framework.

JUnit Jupiter – The API for writing JUnit 5 tests which also allows the core functionality of JUnit 4 to be extended. Contains a TestEngine for running these JUnit 5 tests.

JUnit Vintage – Provides a TestEngine for running JUnit 3 and 4 tests on its platform. Ensures backwards compatibility so that older tests will still work.

As one major goal of the JUnit team is to “create an up-to-date foundation for developer-side testing on the JVM” it makes sense that the new JUnit requires a newer JDK version so that the newer Java features like lambdas can be used. This means that JUnit 5 tests can only run on Java 8 or later JDKs. You can still create and compile tests on older JDKs but you simply cannot run them.

New Features

These are just a few of the new features that people already using JUnit 5 are enjoying:

1) @DisplayName 
A display name can now be set for a test. This will be shown instead of the method name and will make output both easier to read and format. You can even include spaces and emojis.

2) Dynamic Tests
Dynamic on-the-fly tests can be made using @TestFactory and lambdas. These tests must return instances of Streams, Collections, Iterables, or Iterators of DynamicNode instances. These cases are executed lazily and so is generated at run-time.

@TestFactory
Collection<DynamicTest> dynamicTestsFromCollection() {
   return Arrays.asList(
      dynamicTest("Test True", () -> assertTrue(true)),
      dynamicTest("Test Equals", () -> assertEquals(5, 2 + 3))
   );
}


3) Lazy 
Initialization
As mentioned in the previous section we can now use lazy initialization thanks to allowance of Lambdas. These aren’t only used in @TestFactory test cases though they can be used to generate dynamic error messages. Meaning that if you have a method that takes a while to create the error message then it will only execute once the assertion fails instead of every time.

4) Multiple assertions at once
We now have the power to group assertions together using the assertAll method. It will process all of the assertions in a group even if one fails. Then it will let us know which assertions failed at the end so we do not have to fix  and rerun one at a time.

assertAll( 
  ()-> assertEquals(1, 2), 
  () -> assertTrue(3 < 4), 
);


5) Assumptions

These were already present in JUnit 4 but I thought they were worth a mention because in JUnit 5 they can also use Java 8 lambdas. According to the JUnit 5 user guide “Assumptions provide a basic form of dynamic behavior but are intentionally rather limited in their expressiveness”. They are useful because a failed Assumption does not fail a test,  it aborts it. This is useful if you only want to perform tests under certain conditions like on certain platforms or only if certain variables are present in the current runtime environment.

@Test
void testOnlyOnCertainMachines() {
   assumeTrue("dev".equals(System.getenv("ENV")),
   () -> "Aborting test as not needed on this computer");
   // rest of the test to run
}


6) ParameterizedTests
These allow you to run a test case multiple times with different arguments. These arguments can be strings, literal values, methods, Enums, CSV files, etc. @ParameterisedTest ultimately lets you avoid using unnecessary testing loops or duplicating test code.

@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
   assertTrue(argument > 0 && argument < 4);
}

And there you have just a taste of some of the new features JUnit 5 has got to offer. Check out the user guide for a more complete list.