JUnit 5 – Basics

With JUnit 5 being set up we can write some basic tests, getting to know the test lifecycle, assertions, assumptions, disabled and nested tests.

Overview

This post is part of a series about JUnit 5:

This series is based on the pre-release version Milestone 4 and will get updated when a new milestone or the GA release gets published. Another good source is the JUnit 5 user guide. You can find all code samples on GitHub.

Philosophy

The new architecture, which is not terribly important at this moment, is aimed at extensibility. It is possible that someday very alien (at least to us run-of-the-mill Java devs) testing techniques will be possible with JUnit 5.

But for now the basics are very similar to version 4. JUnit 5’s surface undergoes a deliberately incremental improvement and developers should feel right at home. At least I do and I think you will, too:

See? No big surprises.

The Basics Of JUnit 5

Visibility

The most obvious change is that test classes and methods do not have to be public anymore. Package visibility suffices but private does not. I think this is a sensible choice and in line with how we intuit the different visibility modifiers.

Great! I’d say, less letters to type but you haven’t been doing that manually anyways, right? Still less boilerplate to ignore while scrolling through a test class.

Test Lifecycle

@Test

The most basic JUnit annotation is @Test, which marks methods that are to be run as tests.

It is virtually unchanged, although it no longer takes optional arguments. Expected exceptions and timeouts can now be verified with assertions.

JUnit 5 creates a new test instance for each test method (same as JUnit 4).

Before And After

You might want to run code to set up and tear down your tests. There are four method annotations to help you do that:

@BeforeAll
Executed once; runs before the tests and methods marked with @BeforeEach.
@BeforeEach
Executed before each test.
@AfterEach
Executed after each test.
@AfterAll
Executed once; runs after all tests and methods marked with @AfterEach.

Because a new instance is created for each test, there is no obvious instance on which to call the @BeforeAll/ @AfterAll methods, so they have to be static.

The order in which different methods within the same class that bear the same annotation are executed is undefined. The same is not true for inherited methods with the same annotation, which are executed in a top-down fashion for lifecycle methods that are executed before a test and bottom-up for those running after a test.

Except in name, these annotations work exactly like in JUnit 4. While not uncommon, I wasn’t initially convinced of the names but by now I got used to them – I wonder which one is which only about once a day.

Disabling Tests

It’s Friday afternoon and you just want to go home? No problem, just slap @Disabled on the test (optionally giving a reason) and run.

Test Class Lifecycle

It is interesting to note that the test class lifecycle didn’t make it past the prototype. It would run all tests on the same instance of the test class, thus allowing the tests to interact with each other by mutating state. Support for that use case, called scenario tests, is planned, though.

As I already wrote while discussing the prototype: I think this is a typical case of a feature that is harmful in 99% of the cases but indispensable in the other 1%. Considering the very real risk of horrible inter-test-dependencies I’d say it was a good thing that it was taken out in its original form.

Assertions

If @Test, @Before..., and @After... are a test suite’s skeleton, assertions are its flesh. After the instance under test was prepared and the functionality to test was executed on it, assertions make sure that the desired properties hold. If they don’t, they fail the running test.

Classic

Classic assertions either check a property of a single instance (e.g. that it is not null) or do some kind of comparison (e.g. that two instances are equal). In both cases they optionally take a message as a last parameter, which is shown when the assertion fails. If constructing the message is expensive, it can be specified as a lambda expression, so construction is delayed until the message is actually required.

As you can see, JUnit 5 doesn’t change much here. The names are the same as before and comparative assertions still take a pair of an expected and an actual value (in that order).

That the expected-actual order is so critical in understanding the test’s failure message and intention, but can be mixed up so easily is a big blind spot. There’s nothing much to do, though, except to create a new assertion framework. Considering big players like Hamcrest (ugh!) or AssertJ (yeah!), this would not have been a sensible way to invest the limited time. Hence the goal was to keep the assertions focused and effort-free.

New is that failure message come last. I like it because it keeps the eye on the ball, i.e. the property being asserted. As a nod to Java 8, Boolean assertions now accept suppliers, which is a nice detail.

Extended

Aside from the classical assertions that check specific properties, there are a couple of others.

The first is not even a real assertion, it just fails the test with a failure message.

Then we have assertAll, which takes a variable number of assertions and tests them all before reporting which failed (if any).

This is great to check a number of related properties and get values for all of them as opposed to the common behavior where the test reports the first one that failed and you never know the other values.

To compare collections you can use assertArrayEquals and assertIterableEquals, which work like you would expect: the given arrays or iterables need to contain the same number of elements and these elements must be pairwise equal in the order in which they are encountered.

A special case of comparing collections is made for lists of strings. The use case are log messages or other textual reporting results that need to be compared to verify a system is running as expected. In it’s simplest case it compares the string lists element by element, but it can also do regular expression matching (where expected acts as the regex) or fast forwarding.

This feature was first developed internally to test the console launcher and verify whether it creates the correct output.

Finally we have assertThrows, which fails the test if the given method does not throw the specified exception. It also returns the exceptions so it can be used for further verification, e.g. asserting that the message contains certain information.

Alternatives

The communicaton between assertions and the test framework is usually very loose and happens via exceptions. JUnit 5 keeps this approach, which means alternative assertion libraries like Hamcrest, AssertJ, or Google Truth work in JUnit 5 without changes.

Assumptions

Assumptions allow to only run tests if certain conditions are as expected. This can be used to reduce the run time and verbosity of test suites, especially in the failure case.

Assumptions can either be used to abort tests whose preconditions are not met or to execute (parts of) a test only if a condition holds. The main difference is that aborted tests (the first two) are reported as disabled, whereas a test that was empty because a condition did not hold (the last one) is plain green.

Nesting Tests

JUnit 5 makes it near effortless to nest test classes. Simply annotate inner classes with @Nested and all test methods in there will be executed as well:

As you can see, @BeforeEach (and @AfterEach) work here as well. Although currently not documented the initializations are executed outside-in. This allows to incrementally build a context for the inner tests.

For nested tests to have access to the outer test class’ fields, the nested class must not be static. Unfortunately this forbids the use of static methods so @BeforeAll and @AfterAll can not be used in that scenario.

Maybe you’re asking yourself what this is good for. I use nested test classes to inherit interface tests, others to keep their test classes small and focused. The latter is also demonstrated by the more elaborate example commonly given by the JUnit team, which tests a stack:

In this example the state is successively changed and a number of tests are executed for each scenario.

Naming Tests

JUnit 5 comes with an annotation @DisplayName, which gives developers the possibility to give more easily readable names to their test classes and methods.

With it, the stack example from looks as follows:

This creates nicely readable output and should bring joy to the heart of BDD‘ers!

junit-5-basics-testing-a-stack

Reflection

That’s it, you made it! We rushed through the basics of how to use JUnit 5 and now you know all you need to write plain tests: How to annotate the lifecycle methods (with @[Before|After][All|Each]) and the test methods themselves ( @Test), how to nest ( @Nested) and name ( @DisplayName) tests and how assertions and assumptions work (much like before).

But wait, there’s more! We didn’t yet talk about conditional execution of tests methods, the very cool parameter injection, the extension mechanism, or the project’s architecture. For that, check out the other articles in this series.

Share & Follow

You liked this post? Then share it with your friends and followers!
twittergoogle_plusredditlinkedin
And if you like what I'm writing about, why don't you follow me?
twittergoogle_plusrss

Other Posts