Assumptions and Conditional Test Execution with JUnit 4 and 5

Sometimes, a test should only be run under certain conditions. One such case are integration tests which depend on a certain external system. We don’t want our builds to fail if that system has an outage, so we just want to skip the tests that need a connection to it. This article shows how you can skip tests in JUnit 4 and JUnit 5 depending on certain conditions.

Example Code

This article is accompanied by a working code example on GitHub.

Assumptions

Both JUnit 4 and JUnit 5 support the concept of assumptions. Before each test, a set of assumptions can be made. If one of these assumptions is not met, the test should be skipped.

In our example, we make the assumption that a connection to a certain external system can be established.

To check if a connection can be established, we create the helper class ConnectionChecker:

public class ConnectionChecker {

  private String uri;

  public ConnectionChecker(String uri){
    this.uri = uri;
  }

  public boolean connect() {
    ... // try to connect to the uri 
  }

}

Our ConnectionChecker has a single public method connect() which sends an HTTP GET request to a given URI and returns true if the server responded with an HTTP response with a status code in the range of 200-299 meaning that the response was successfully processed.

Assumptions for a single Test Method (JUnit 4 and JUnit 5)

Skipping a single test method based on an assumption works the same in JUnit 4 and JUnit 5:

public class ConnectionCheckingTest {

  private ConnectionChecker connectionChecker = 
      new ConnectionChecker("http://my.integration.system");

  @Test
  public void testOnlyWhenConnected() {
    assumeTrue(connectionChecker.connect());
    ... // your test steps
  }

}

The lines below assumeTrue() will only be called if a connection to the integration system could successfully be established.

Most of the times, though, we want all methods in a test class to be skipped depending on an assumption. This is done differently in JUnit 4 and JUnit 5

Assumptions for all Test Methods with JUnit 4

In JUnit 4, we have to implement a TestRule like this:

public class AssumingConnection implements TestRule {

  private ConnectionChecker checker;

  public AssumingConnection(ConnectionChecker checker) {
    this.checker = checker;
  }

  @Override
  public Statement apply(Statement base, Description description) {
    return new Statement() {
      @Override
      public void evaluate() throws Throwable {
        if (!checker.connect()) {
          throw new AssumptionViolatedException("Could not connect. Skipping test!");
        } else {
          base.evaluate();
        }
      }
    };
  }

}

We use our ConnectionChecker to check the connection and throw an AssumptionViolatedException if the connection could not be established.

We then have to include this rule in our JUnit test class like this:

public class ConnectionCheckingJunit4Test {

  @ClassRule
  public static AssumingConnection assumingConnection = 
      new AssumingConnection(new ConnectionChecker("http://my.integration.system"));

  @Test
  public void testOnlyWhenConnected() {
    ...
  }

}

Assumptions for all Test Methods with JUnit 5

In JUnit 5, the same can be achieved a little more elegantly with the extension sytem and annotations. First, we define ourselves an annotation that should mark tests that should be skipped if a certain URI cannot be reached:

@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(AssumeConnectionCondition.class)
public @interface AssumeConnection {

  String uri();

}

In this annotation we hook into the JUnit 5 extension mechanism by using @ExtendWith and pointing to an extension class. In this extension class, we read the URI from the annotation and call our ConnectionChecker to either continue with the test or skip it:

public class AssumeConnectionCondition implements ExecutionCondition {

  @Override
  public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
    Optional<AssumeConnection> annotation = findAnnotation(context.getElement(), AssumeConnection.class);
    if (annotation.isPresent()) {
      String uri = annotation.get().uri();
      ConnectionChecker checker = new ConnectionChecker(uri);
      if (!checker.connect()) {
        return ConditionEvaluationResult.disabled(String.format("Could not connect to '%s'. Skipping test!", uri));
      } else {
        return ConditionEvaluationResult.enabled(String.format("Successfully connected to '%s'. Continuing test!", uri));
      }
    }
    return ConditionEvaluationResult.enabled("No AssumeConnection annotation found. Continuing test.");
  }

}

We can now use the annotation in our tests either on class level or on method level to skip tests conditionally:

@AssumeConnection(uri = "http://my.integration.system")
public class ConnectionCheckingJunit5Test {

  @Test
  public void testOnlyWhenConnected() {
    ...
  }

}

Conclusion

Both JUnit 4 and JUnit 5 support the concept of assumptions to conditionally enable or disable tests. However, it’s definitely worthwhile to have a look at JUnit 5 and its extension system since it allows a very declarative way (not only) to create conditionally running tests.

Tom Hombergs

As a professional software engineer, consultant, architect, general problem solver, I've been practicing the software craft for more fifteen years and I'm still learning something new every day. I love sharing the things I learned, so you (and future me) can get a head start. That's why I founded reflectoring.io.

Recent Posts

Distribute Static Content with Amazon CloudFront

Distribute Static Content with Amazon CloudFront

Amazon CloudFront is a fast content delivery network (CDN) service that securely delivers data, videos, applications, and APIs to customers globally with low latency.

Read more

One-Stop Guide to Mapping with MapStruct

When we define multi-layered architectures, we often tend to represent data differently at each layer. The interactions between each layer become quite tedious and cumbersome.

Read more
Using a Jump host to access an RDS database in a private subnet

Using a Jump host to access an RDS database in a private subnet

Back-end server resources like databases often contain data that is critical for an application to function consistently. So these resources are protected from public access over the internet by placing them in a private subnet.

Read more