If you are a Java developer then chances are high you are using JUnit in your project – the most popular unit testing framework for Java. The current stable version 4.12 is the latest in the JUnit 4 line that was started around 2005 after the introduction of annotations in Java 5. More then ten years later, with Java 8 and lambdas being around, we are facing the next evolutionary step: JUnit 5.
This week, the JUnit 5 team released the first alpha version, asking the community for feedback on the API, missing features and bugs.
What’s new in JUnit 5?
First of all, JUnit 5 is a complete rewrite and requires Java 8, though it can test Java code that is written in older Java versions. It allows using lambdas in assertions, a feature you might already know from the popular assertion library AssertJ. Unlike its predecessor, JUnit 5 is not an all-in-one library but instead provides a set of well-structured modules: The API lives in its own module, separated from the engine, the launcher and integration modules for Gradle and Surefire. The JUnit team even launched an initiative called Open Test Alliance for the JVM to come up with a set of standard exceptions which they hope other testing frameworks as well as IDEs and tools will build upon in the future.
JUnit 5 tests look a lot like JUnit 4 tests: just create a class and add test methods annotated with @Test. However, JUnit 5 provides a completely new set of annotations which resides in a different package than their JUnit 4 counterparts. In addition the assertion methods moved from org.junit.Assert to org.junit.gen5.api.Assertions. Let’s see a simple test class:
import org.junit.gen5.api.Assertions;
import org.junit.gen5.api.Test;
public class Test1 {
@Test
public void test() {
Assertions.assertEquals(3 * 6, 18);
Assertions.assertTrue(5 > 4);
}
}
Assertions
The available assertion methods are similiar to JUnit 4. The Assertions class provides assertTrue, assertEquals, assertNull, assertSame and their negative equivalents. What’s new is the overloaded versions of these methods that expect a lambda expression to supply the assertion message or boolean condition. On top of that, there is a new feature called Grouped Assertions which allows the execution of a group of assertions and have failures reported together. Remember when you were told not to put multiple assertions into one test to avoid assertions not being executed after a previous failure? That is no longer the case now. Just group the respective assertions together.
Another improvement over JUnit 4 is the way to assert on expected exceptions. Instead of putting the expected exception type into the @Test annotation or wrapping your test code into a try-catch you can use assertThrows and equalsThrows.
Let’s see JUnit 5 assertions in a sample test class:
public class Test2 {
@Test
public void lambdaExpressions() {
// lambda expression for condition
assertTrue(() -> "".isEmpty(), "string should be empty");
// lambda expression for assertion message
assertEquals("foo", "foo", () -> "message is lazily evaluated");
}
@Test
public void groupedAssertions() {
Dimension dim = new Dimension(800, 600);
assertAll("dimension",
() -> assertTrue(dim.getWidth() == 800, "width"),
() -> assertTrue(dim.getHeight() == 600, "height"));
}
@Test
public void exceptions() {
// assert exception type
assertThrows(RuntimeException.class, () -> {
throw new NullPointerException();
});
// assert on the expected exception
Throwable exception = expectThrows(RuntimeException.class, () -> {
throw new NullPointerException("should not be null");
});
assertEquals("should not be null", exception.getMessage());
}
}
Assumptions, tags and disabled tests
These three features have already been a part of JUnit 4 and are almost the same in JUnit 5. For assumptions, there are some extra methods to allow lambda expressions now. The idea of assumptions is to skip the test execution if the assumption condition is not met. Tags are the equivalent of the experimental Categories in JUnit 4 and can be applied to test classes and methods. This can be used to enable/disable tests with specific tags in a build. In order to disable tests in your test code use the @Disabled annotation which is equivalent to @Ignore in JUnit 4.
public class Test3 {
@Test
@Disabled
public void disabledTest() {
// ...
}
@Test
@Tag("jenkins")
public void jenkinsOnly() {
// ...
}
@Test
public void windowsOnly() {
Assumptions.assumeTrue(System.getenv("OS").startsWith("Windows"));
// ...
}
}
Extension model
JUnit 5 provides a new extension API that supersedes the previous @RunWith and @Rule extension mechanism. While JUnit 4 test classes were limited to only one Runner, the new extension model allows the registration of multiple extensions for a class or even a method.
@ExtendWith(MockitoExtension.class)
@ExtendWith(CdiUnitExtension.class)
public class Test4 {
@Test
@DisplayName("awesome test")
void dependencyInjection(TestInfo testInfo) {
assertEquals("awesome test", testInfo.getDisplayName());
}
}
There are built-in extensions in JUnit 5 that allow for method-level dependency injection. Without going into details here, I put an example in the above code sample that injects a TestInfo instance into the method.
Can I use Hamcrest Matchers or AssertJ with JUnit 5?
Yes, you can. Just use the assertion methods provided by Hamcrest and AssertJ instead of the JUnit 5 methods. Be aware that there is no direct support for Hamcrest matchers anymore as in JUnit 4’s assertThat method.
public class Test5 {
@Test
public void emptyString() {
// JUnit 5
org.junit.gen5.api.Assertions.assertTrue("".isEmpty());
// AssertJ
org.assertj.core.api.Assertions.assertThat("").isEmpty();
// Hamcrest
org.hamcrest.MatcherAssert.assertThat("", isEmptyString());
}
}
What’s next?
There is no roadmap on the project’s GitHub page. JUnit 5 is alpha now, feel free to test it and give your feedback. The team has built integration modules for Gradle and Maven Surefire. If you would like to run JUnit 5 tests in your IDE you can use the JUnit 4 Runner. Once the JUnit 5 API is stable, IDE and tool vendors are supposed to provide direct integration. It will also be interesting to see if the common exception API that has been extracted by the Open Testing Alliance for the JVM will be adapted by other testing frameworks.