JUnit 5 Conditions: @Enabled, @Disabled, And Customized

It is time to combine two topics that we’ve been exploring in the past: conditions like @DisabledOnOs and @DisabledOnJre on the one hand and extending JUnit 5 with custom behavior on the other hand. In that last post I left you with the promise to look at conditions, which allow us to define flexible criteria for (de)activating tests, just like the built-in @Disabled.../ @Enabled... annotations.

I’ll start by giving you a run-down of Jupiter’s conditions before having a quick look at their implementation to prepare you for the last part: creating custom conditions. Conditions are a great way to get started with extensions because they are so easy to write and make a test suite so much more readable than checking the same condition in the test methods.

Overview

This post is part of a series about JUnit 5:

This series is based on JUnit 5.2.0 and will get updated as new releases come out. Another good source is the JUnit 5 user guide. You can find all code samples in my JUnit 5 demo project on GitHub.

Conditions In JUnit 5

Besides @Disabled, which unconditionally disables either a single test method or an entire test class, depending on where it is applied, Jupiter comes with four conditions. They evaluate the operating system, the Java version, a system property, or an environment variable:

@DisabledOnOs / @EnabledOnOs
Given either a single or multiple values of the OS enum, tests can be disabled on selected operating systems.
@DisabledOnJre / @EnabledOnJre
Given either a single or multiple values of the JRE enum, tests can be disabled when the suite runs on selected Java versions.
@DisabledIfSystemProperty / @EnabledIfSystemProperty
These conditions have two attributes, named and matches. The first names a specific system property (surprise!) and the second is a regular expression that is matched against the property’s value. If it matches, the test ist disabled or not (depending on which annotation you use).
@DisabledIfEnvironmentVariable / @EnabledIfEnvironmentVariable
Work exactly like @DisabledIfSystemProperty and @EnabledIfSystemProperty, but check environment variables, not system properties.

@Enabled…(X) doesn’t “enable on X”, rather it “disables on everything but X”

These conditions always come in two variants, @Disabled... and @Enabled..., but it is important to understand that enabled on X really just means disabled on everything but X. That becomes relevant when you use several conditions of different kinds: As soon as one of them disables a test, the test does not run, no matter what other @Enabled... conditions might have to say about that:

You should only ever use one condition of the same kind because JUnit only evaluates the first it finds – all others are silently ignored.

Disabling All Conditions

You will sometimes want to run disabled tests to find out whether they indeed break under the avoided circumstances. Don’t worry, you don’t have to remove all @Disabled... annotations. Instead configure JUnit with the parameter junit.jupiter.conditions.deactivate.

The given value is interpreted as a glob pattern against which the class name of each ExecutionCondition implementation (see below) is compared. If they match, the condition will be ignored and the test hence be activated. With * you can deactivate all conditions and hence run all tests.

One way to pass this parameter is as a system property with -Djunit.jupiter.conditions.deactivate=*.

Extension Points For Conditions

Remember what we said about extension points? No? In short: There’s a bunch of them and each relates to a specific interface. Implementations of these interfaces can be handed to JUnit (for example, with with the @ExtendWith annotation) and it calls them at the appropriate time.

For conditions, there is the interface ExecutionCondition:

And that’s already pretty much it. Any condition has to implement that interfaces and execute the required checks in its evaluate implementation.

@Disabled

The easiest condition is one that is not even evaluated: We simply always disable the test if the annotation is present. That’s how @Disabled works:

And the matching extension:

Easy as pie, right? If it wouldn’t exist already, our implementation of @Disabled would be almost exactly the same – the only difference is that we would have to put @ExtendWith(@DisabledCondition.class) on @Disabled.

Now let’s look at something slightly less trivial.

@DisabledOnOs And Other Conditions

The only difference between @Disabled and @DisabledOnOs (et al.) is that the latter check something else besides the presence of an annotation to determine whether a test is disabled:

A Custom Condition: @EnabledIfReachable

For our own condition, lets check whether a URL is reachable within a specified time frame. Once again, we start with the annotation:

We want to allow enabling individual tests as well as an entire class and, being good JUnit 5 citizens, we prepare our annotation for composability via meta-annotations, so we use the targets METHOD, TYPE, and ANNOTATION_TYPE. Because we’re not part of Jupiter core, we actually have to extend our annotation with the condition implementation. As attributes we define the URL and the timeout in milliseconds.

Now, on to the condition:

Given what we saw earlier, this is all pretty straightforward. To check actual reachability in pingUrl, I used an implementation from StackOverflow, but the details don’t really matter.

And that’s already it. Here’s how @EnabledIfReachableCondition looks in action:

Summary

Now you know how to implement conditions in JUnit Jupiter:

  • create the desired annotation and @ExtendWith your condition implementation
  • implement ExecutionCondition
  • check whether your annotation is present
  • perform the actual checks and return the result

This way, your custom condition is just as usable as the built-in @DisabledOnOs, @DisabledOnJre, @DisabledIfSystemProperty, @DisabledIfEnvironmentVariable, and their @Enabled... counterparts.

To deactivate conditions and run all tests, pass the following system property: -Djunit.jupiter.conditions.deactivate=*.

For more fun with flags conditions and other extension points, check the next posts 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_plusrssmail

Other Posts