Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.
See all our reports

JUnit 5: Next Generation Testing on the JVM with Nicolai Parlog

Hi there. It’s been a while, but we finally caught up with the current Virtual JUG sessions and in this blog we present a recap of the wonderful and very practical presentation: JUnit 5: Next Generation Testing on the JVM.

For this presentation, the speaker was none other than Nicolai Parlog, a JVM aficionado, a software engineer with tons of experience, and a frequent conference speaker and blogger. You can find Nicolai’s blog at codefx.org and I can promise you’ll learn a lot.

Nicolai constantly reads, thinks, and writes about the software development, and codes for a living as well as for fun, so he has the expertise to share with the world.

In any case, before we proceed, be sure to follow Nicolai on Twitter: @nipafx and if something remains unclear in the session ping him and he’d be happy to chat with you!

So the session is available on the Virtual JUG’s Youtube channel, or just click play on the embedded video and start learning:

JUnit 5: Next Generation Testing on the JVM

Well, if for the last ten years you haven’t been living under a rock, or not on the JVM, you probably have at least some experience with JUnit. You’ll know it’s the most used library to write unit tests in for Java projects.

JUnit is quite mature and is pretty good as libraries go, however recently, after the release of the JDK 8, the JUnit team worked hard and delivered a major rewrite of the test engine. One of the main syntactical reasons was to adopt lambdas, so you can write code that is full of them and benefit from unit tests that use a similar code style. However, the differences between JUnit version 4 (or older, if you’re still half under the rock) and the fresh version 5 do not stop there.

The Basics

The most basic test using JUnit 5 can look something like this:

class JUnit5Test {
    @Test
    void someTest() {
        assertTrue(true);
    }
}

If you’ve dealt with the JUnit before the first thing you notice that now you don’t have to declare everything as public, the default visibility suffices for the tests. This can help you by allowing you to better structure your tests as well as not importing them by mistake into your production code!

Another minor, but pleasant change is the renamed lifecycle methods for setting up the fixtures and setting up the tests:

@BeforeAll
static void beforeAll() { ... }
@BeforeEach
void beforeEach() { ... }
@AfterEach
void afterEach() { ... }
@AfterAll
static void afterAll() { ... }

Then we have one of the major changes to the core functionality of JUnit. The assertions methods have now been rewritten to accept lambdas for the message creation. And the best part is that you can group several assertions together and evaluate them together. This will capture all the assertion errors and report them together rather than stop after the first error.

@Test
void assertAllProperties() {
    Address ad = new Address(
      "City", "Street", "42");

    assertAll("address",
      () -> assertEquals("C", ad.city),
      () -> assertEquals("Str", ad.street),
      () -> assertEquals("63", ad.number)
    );
}

Isn’t that cool? Now you’ll have even more goodness with JUnit 5. The @Nested annotation will allow you to better control the order of your test executions. On top of that you now have the ability to control the reporting names of your test cases. Use the @DisplayName annotation to specify what you want to see in the report for that method.

@DisplayName("A count")
class CountTest {
    @Nested
    @DisplayName("when greater zero")
    class CountGreaterZero {
        @Test
        @DisplayName("is positive")
        void isPositive() { ... }
    }
}

Dynamic tests

Next, Nicolai showed some more advanced features that can make your testing much easier. One problem with the JUnit 4 and older libraries was that you had to specify all the tests during compile time. You needed to declare all the methods and their assertions in the code.

JUnit 5 supports the dynamic test declaration. It offers an API to create the test cases on the fly, at runtime and you can use it, for example to fetch the data for the test cases from an external location.

To create dynamic tests you need to annotate a method with the @TestFactory annotation and make it return a collection (or a stream, or an iterable, JUnit is pretty flexible) of type DynamicTest.

The example below returns a list consisting of two dynamic tests.

@TestFactory
List<DynamicTest> createPointTests() {
    return Arrays.asList(
        dynamicTest(
            "A Great Test For Point",
            () -> { /* test code */ } ),
        dynamicTest(
            "Another Great Test For Point",
            () -> { /* test code */ } )
    );
}

Note that the code inside the dynamic test, that you can specify as a lambda, you’ll use the default assertion method and will have all the support that JUnit provides for the normal compile time tests.

Extensions

One of the main JUnit 5 principles is: prefer extension points over features. And to support the use of the extension points, the JUnit team has introduced another annotation: @ExtendWith.

JUnit 5 provides a comprehensive list of extensions points that you can use straight out of the box, including:

  • Test Instance Post Processor
  • BeforeAll Callback
  • Test and Container Execution Condition
  • BeforeEach Callback
  • Parameter Resolution
  • Before Test Execution
  • After Test Execution
  • Exception Handling
  • AfterEach Callback
  • AfterAll Callback

Imagine for example that you want to report the execution time for each test. You’ll need to implement an extension:

public class BenchmarkExtension implements
        BeforeTestExecutionCallback,
        AfterTestExecutionCallback {

    private long launchTime;
    @Override
    public void beforeTestExecution(
            TestExtensionContext context) {
        launchTime = System.currentTimeMillis();
    }
    @Override
    public void afterTestExecution(
            TestExtensionContext context) {
        printf("Test '%s' took %d ms.%n",
            context.getDisplayName(),
            System.currentTimeMillis() - launchTime);
    }
}

Your extension will implement the necessary callbacks to receive the control flow at the right times of the test execution. Then you can go wild with whatever code you want to implement. The example above simply captures two timestamps, before and after the test and reports the difference as the time it took to execute the test.

You can also implement all kinds of conditional executions. A great example of this is the @Disabled annotation that prevents JUnit from executing the test in question.

Then you just slap the @ExtendWith annotation on your test class or method and it will make JUnit engine to call your extension when needed. One particularly useful application of this is the parameter resolvers: you can specify the parameters for your test methods and they will be provided from the general test context.

To get the exact details on how to use them, you should take your time and watch the session in full. Nicolai did a wonderful job at explaining all the gritty details, provided tons of examples, and in general presented the material very well.

You’ll also learn a thing or two about the new architecture of the JUnit project, how the test engine is different from what we had earlier and what are the best ways to set up the project using JUnit or how to properly enable JUnit in the continuous integration environments.

Useful links to the resources

Here are the links to the resource Nicolai mentioned in the session and a couple we found quite useful afterwards.

Also the slides for this Virtual JUG presentation are available here!

If you use Jenkins as your CI server, you can check out the Jenkins JUnit plugin. If you’re just setting up a new project, Nicolai has a great guide for how to setup JUnit properly.

A friendly chat with Nicolai

After most vJUG sessions we interview the speaker and ask them more questions about the general state of affairs on the JVM, in the community, and what’s the best way to learn the topic they are proficient at. Nicolai is no exception, he took time to sit with us and now you can enjoy the result of that chat too. If you have any further questions or just want to say thanks to Nikolai, ping him on Twitter.




Read next: