JUnit 5 Assumptions: assumeTrue, assumeFalse, assumingThat

JUnit 5 assumptions skip a test when the environment is not right for it. Learn assumeTrue(), assumeFalse(), assumingThat() and abort(), how aborted tests differ from failed ones, and when to use @EnabledOnOs and other conditional annotations instead.

junit5 logo

JUnit 5 assumptions are checks that stop a test early when the environment is not right for it, so the test is reported as skipped instead of failed. We use them when a test depends on something that may be missing on the current machine, such as a database URL or a specific operating system. The assumption methods are static members of the org.junit.jupiter.api.Assumptions class.

The following example lets a test run only when the CI environment variable exists, which GitHub Actions and GitLab CI set to true.

@Test
void publishesReportOnCi() {
  assumeTrue(System.getenv("CI") != null, "Not running on CI");   // aborted on a laptop

  assertEquals("true", System.getenv("CI"));                      // runs only on CI
}

Notice that on a laptop, assumeTrue() throws TestAbortedException at the first line, and Maven Surefire counts the test as skipped. The build still succeeds in both runs.

[INFO] Running com.howtodoinjava.assumptions.CiOnlyTest
[WARNING] Tests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.312 s -- in com.howtodoinjava.assumptions.CiOnlyTest
[INFO] BUILD SUCCESS
[INFO] Running com.howtodoinjava.assumptions.CiOnlyTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.157 s -- in com.howtodoinjava.assumptions.CiOnlyTest
[INFO] BUILD SUCCESS

Next, we compare an assumption with an assertion and go through assumeTrue(), assumeFalse(), assumingThat() and abort() one by one. After that, we see what happens when an assumption fails in @BeforeEach or @BeforeAll, and when a conditional annotation such as @EnabledOnOs is the better choice. The examples use Java 25 and JUnit 6.1.3, where the Assumptions API is the same as in JUnit 5.

1. Assumption vs Assertion

An assertion checks the result of the code under test, whereas an assumption checks a precondition of the test itself. Both throw an exception when the condition is false, but the exception types differ, and JUnit reports the two cases differently.

  • When an assertion fails, assertTrue() throws AssertionFailedError, and JUnit marks the test as failed.
  • When an assumption is invalid, assumeTrue() throws org.opentest4j.TestAbortedException, and JUnit marks the test as aborted. Maven and most IDEs show an aborted test as skipped.

The two tests in the next snippet check the same condition. The test.db.url system property is not set, so both conditions are false.

boolean databaseConfigured = System.getProperty("test.db.url") != null;    // false

@Test
void withAssumption() {
  assumeTrue(databaseConfigured, "test.db.url is not set");    // aborted (skipped)
}

@Test
void withAssertion() {
  assertTrue(databaseConfigured, "test.db.url is not set");    // failed
}

Maven counts one test as skipped and one as failed, and only the failed test breaks the build.

[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 1, Time elapsed: 0.258 s <<< FAILURE! -- in com.howtodoinjava.assumptions.AssumptionVsAssertionDemoTest
[ERROR] com.howtodoinjava.assumptions.AssumptionVsAssertionDemoTest.withAssertion -- Time elapsed: 0.147 s <<< FAILURE!
org.opentest4j.AssertionFailedError: test.db.url is not set ==> expected: <true> but was: <false>
...
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 1
[INFO] BUILD FAILURE

So we use an assumption when a false condition means the test cannot run here, and an assertion when it means the code is broken. A missing database on a developer laptop is not a bug, so the test should skip. A wrong total in an invoice is a bug, so the test should fail.

Flow diagram: a test method starts and calls assumeTrue(condition). If the condition is false, assumeTrue throws TestAbortedException, the test is aborted, Maven reports Skipped: 1 and the build succeeds. If the condition is true, the test body runs; when every assertion passes the test passes, and when an assertion is false it throws AssertionFailedError, the test fails and Maven prints BUILD FAILURE.
An invalid assumption ends the test as aborted (skipped). Only a failed assertion breaks the build.

2. JUnit 5 Assumptions Methods

The Assumptions class has four groups of static methods. Each of assumeTrue() and assumeFalse() takes the condition as either a boolean or a BooleanSupplier (a lambda that returns a boolean), with an optional message.

MethodTest continues whenWhen the check does not passOverloads
assumeTrue(condition)condition is truethrows TestAbortedException, test aborted6
assumeFalse(condition)condition is falsethrows TestAbortedException, test aborted6
assumingThat(condition, executable)alwaysskips only the executable block2
abort(message)neveralways throws TestAbortedException3

The message can be a String or a Supplier<String>. JUnit calls a message supplier only when the assumption fails, so we use a supplier when building the message is costly, for example when it formats a large object.

assumeTrue(boolean assumption)
assumeTrue(boolean assumption, String message)
assumeTrue(boolean assumption, Supplier<String> messageSupplier)
assumeTrue(BooleanSupplier assumptionSupplier)
assumeTrue(BooleanSupplier assumptionSupplier, String message)
assumeTrue(BooleanSupplier assumptionSupplier, Supplier<String> messageSupplier)

We import the methods statically, the same way as the JUnit 5 assertions.

import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.Assumptions.assumingThat;
import static org.junit.jupiter.api.Assumptions.abort;

The methods come with the junit-jupiter dependency, so no extra library is needed, and the junit-bom picks its version.

3. Assumptions.assumeTrue()

The assumeTrue() method lets the test continue only when the condition is true. When the condition is false, the method throws TestAbortedException, and the remaining lines of the test never run. For example, an integration test for an order service needs a database URL, which a developer laptop often does not have.

assumeTrue(System.getProperty("os.name").startsWith("Linux"));      // true on Linux: continues

assumeTrue(System.getProperty("test.db.url") != null,
    "test.db.url is not set");                                       // aborted

assumeTrue(() -> Runtime.version().feature() >= 25,                  // BooleanSupplier
    () -> "Needs Java 25, found " + Runtime.version());              // message built only on abort

JUnit puts “Assumption failed” in front of our message. The Surefire XML report stores the message in a skipped element.

<testcase name="runsWhenDatabaseUrlIsSet" classname="com.howtodoinjava.assumptions.AssumeTrueTest" time="0.0">
  <skipped type="org.opentest4j.TestAbortedException"><![CDATA[org.opentest4j.TestAbortedException: Assumption failed: test.db.url is not set

When we pass the property on the command line, Surefire forwards it to the test JVM as a system property, and all three tests run.

mvn test -Dtest=AssumeTrueTest -Dtest.db.url=jdbc:h2:mem:test
# Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

4. Assumptions.assumeFalse()

The assumeFalse() method is the opposite of assumeTrue(). The test continues only when the condition is false, so we use it to exclude one environment and keep all the others.

assumeFalse(System.getProperty("os.name").startsWith("Windows"), "Uses POSIX paths");   // skipped on Windows
assumeFalse(System.getenv("CI") != null, "Needs a local browser");                      // skipped on CI

On a laptop, both tests in AssumeFalseTest run. With CI=true, the second test is aborted, and Maven reports Tests run: 2, Failures: 0, Errors: 0, Skipped: 1.

5. Assumptions.assumingThat()

The assumingThat() method runs an Executable (a lambda with no arguments that can throw an exception) only when the condition is true. Unlike the other methods, assumingThat() never aborts the test. When the condition is false, the method skips the block, and the test continues with the next line.

We use assumingThat() when most of a test applies everywhere and only a few assertions depend on the environment. The next test checks a report that joins lines with the line separator of the operating system.

boolean windows = System.getProperty("os.name").startsWith("Windows");
String report = formatter.join(List.of("apple", "banana"));

assumingThat(windows, () -> assertEquals("apple\r\nbanana", report));    // runs on Windows only
assumingThat(!windows, () -> assertEquals("apple\nbanana", report));     // runs on Linux and macOS

assertEquals("== FRUITS ==", formatter.header("fruits"));                // runs everywhere

When the condition is true and the block throws an exception, assumingThat() rethrows the exception as is. So a failed assertion inside the block fails the whole test, as it would outside the block.

6. Assumptions.abort()

The abort() method aborts the test unconditionally. We call it in a branch where we already know the test cannot continue, for example in a switch or after a lookup that found nothing. The method has a form without arguments, plus abort(String message) and abort(Supplier<String> messageSupplier) for a message.

The return type of abort() is generic (<V> V), so we can use the call inside an expression that must return a value. For example, a payment test reads the sandbox URL with Optional.orElseGet() and aborts when the variable is missing. The next snippet uses abort() in Optional.orElseGet() and in a switch expression.

String url = Optional.ofNullable(System.getenv("PAYMENT_SANDBOX_URL"))
    .orElseGet(() -> abort("PAYMENT_SANDBOX_URL is not set"));   // aborted when missing

String shell = switch (os.split(" ")[0]) {
  case "Linux", "Mac" -> "/bin/sh";
  case "Windows" -> "cmd.exe";
  default -> abort("No shell known for " + os);                  // aborted on other systems
};

Unlike assumeTrue(), the method abort() does not add the “Assumption failed” prefix, so the report shows our message unchanged.

<skipped type="org.opentest4j.TestAbortedException"><![CDATA[org.opentest4j.TestAbortedException: PAYMENT_SANDBOX_URL is not set

7. Assumptions in @BeforeEach and @BeforeAll

When every test in a class needs the same precondition, we put the assumption in a lifecycle method instead of repeating it in each test. The result depends on the method we choose.

Assumption inWhat gets abortedCleanup that still runs
test methodthat one test@AfterEach, @AfterAll
@BeforeEacheach test separately, the test methods never run@AfterEach for each test, @AfterAll
@BeforeAllthe whole class, no test method and no @BeforeEach runs@AfterAll

7.1. Assumption in @BeforeEach

A @BeforeEach method runs before every test, so the assumption is checked once per test. With TestInfo, we add the test name to the message.

@BeforeEach
void checkDatabase(TestInfo info) {
  assumeTrue(System.getProperty("test.db.url") != null,
      () -> "test.db.url is not set, skipping " + info.getDisplayName());
}

@AfterEach
void cleanUp(TestInfo info) {
  System.out.println("@AfterEach runs for " + info.getDisplayName());
}

Neither test method prints its name, but the @AfterEach method runs for both tests.

@AfterEach runs for deletesOrder()
@AfterEach runs for savesOrder()
[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 2, Time elapsed: 0.256 s -- in com.howtodoinjava.assumptions.BeforeEachAssumptionTest

7.2. Assumption in @BeforeAll

A @BeforeAll method runs once for the class. When its assumption fails, JUnit aborts the class container (that is, the node in the test tree that groups the tests of the class), so none of the tests start.

@BeforeAll
static void checkSandbox() {
  assumeTrue(System.getenv("PAYMENT_SANDBOX_URL") != null, "PAYMENT_SANDBOX_URL is not set");
}

@AfterAll
static void cleanUp() {
  System.out.println("@AfterAll runs");
}
@AfterAll runs
[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 2, Time elapsed: 0.222 s -- in com.howtodoinjava.assumptions.BeforeAllAssumptionTest

Surefire lists both tests, charge and refund, as skipped with the message Assumption failed: PAYMENT_SANDBOX_URL is not set. The @AfterAll method still runs, so we can close resources that @BeforeAll opened before the assumption.

8. Conditional Annotations as an Alternative

For common checks such as the operating system, the Java version, an environment variable or a system property, JUnit also offers conditional test execution annotations from the org.junit.jupiter.api.condition package. Each annotation expresses one of the checks from the earlier sections, and JUnit writes a fixed skip reason for it.

AssumptionEquivalent annotationSkip reason in the report
assumeTrue(os.startsWith(“Linux”))@EnabledOnOs(OS.LINUX)Disabled on operating system: Linux
assumeTrue(System.getenv(“CI”) != null)@EnabledIfEnvironmentVariable(named = “CI”, matches = “true”)Environment variable [CI] does not exist
assumeTrue(System.getProperty(“test.db.url”) != null)@EnabledIfSystemProperty(named = “test.db.url”, matches = “jdbc:.+”)System property [test.db.url] does not exist
assumeFalse(Runtime.version().feature() == 25)@DisabledOnJre(JRE.JAVA_25)Disabled on JRE version: 25.0.4.1

The matches attribute is a regular expression that the whole value must match. With annotations, our four tests have no assumption calls in their bodies.

@Test
@EnabledOnOs(OS.LINUX)
void runsOnLinux() { ... }

@Test
@EnabledIfEnvironmentVariable(named = "CI", matches = "true")
void publishesReportOnCi() { ... }

@Test
@EnabledIfSystemProperty(named = "test.db.url", matches = "jdbc:.+")
void runsWhenDatabaseUrlIsSet() { ... }

@Test
@DisabledOnJre(value = JRE.JAVA_25, disabledReason = "Known bug on Java 25")
void notOnJava25() { ... }

On a Linux laptop with Java 25, the first test runs and the other three are skipped. Surefire stores the reason of each skipped test in the report.

<testcase name="publishesReportOnCi" ...>
  <skipped message="Environment variable [CI] does not exist"/>
<testcase name="runsWhenDatabaseUrlIsSet" ...>
  <skipped message="System property [test.db.url] does not exist"/>
<testcase name="notOnJava25" ...>
  <skipped message="Disabled on JRE version: 25.0.4.1 ==&gt; Known bug on Java 25"/>

JUnit evaluates an annotation before the test starts, whereas an assumption runs inside the test. So with an annotation, the test is skipped, and JUnit never calls its @BeforeEach methods or the test method. With an assumption, the test is aborted, because the test has already started and @BeforeEach has already run.

We prefer an annotation when the check is about the OS, the JRE, an environment variable or a system property, because the condition is visible on the method signature. We use an assumption when the condition needs code, such as calling a service or using a value computed earlier in the test. For a custom condition that should still skip the test before it starts, the @EnabledIf annotation takes the name of a method that returns a boolean. When the method returns false, the report shows the reason @EnabledIf(“ready”) evaluated to false for a method named ready().

9. How IDEs and Maven Report Aborted Tests

The JUnit Platform has a separate result for aborted tests, but most tools know only passed, failed and skipped tests. The tools map an aborted test to skipped.

  • Maven Surefire counts aborted tests under Skipped and prints the summary line with [WARNING], but the build still ends with BUILD SUCCESS.
  • In the XML report (for example target/surefire-reports/TEST-com.howtodoinjava.assumptions.CiOnlyTest.xml), an aborted test has a skipped element with type=”org.opentest4j.TestAbortedException” and the assumption message. A test disabled by an annotation has a skipped element with only the reason.
  • IntelliJ IDEA and Eclipse show an aborted test with the ignored (skipped) icon, and the assumption message appears in the test output.

We can also customize the Surefire XML reports. When a CI dashboard shows many skipped tests, the assumption messages in these reports tell us which precondition was missing.

10. JUnit Assumptions FAQs

10.1. What Is the Difference Between @Disabled and an Assumption?

@Disabled turns a test off everywhere, whatever the environment, and we use it for a broken or unfinished test. An assumption skips a test only when a condition is false at runtime, so the same test runs on one machine and skips on another. For example, a test for a known bug gets @Disabled with the issue number as the reason, whereas a test that needs a database gets an assumption.

10.2. Does a Failed Assumption Fail the Maven Build?

No. Maven counts the test as skipped, and the build ends with BUILD SUCCESS. Only failed assertions and unexpected exceptions break the build, as the output in section 1 shows.

10.3. How Does an Assumption Work in a Parameterized Test?

A failed assumption aborts only the current invocation, and the other invocations still run. In the next test, the empty string is skipped, and “apple” and “banana” pass.

@ParameterizedTest
@ValueSource(strings = {"apple", "", "banana"})
void formatsHeader(String title) {
  assumeFalse(title.isBlank(), "Empty title");    // aborts the "" invocation only

  assertEquals("== " + title.toUpperCase() + " ==", formatter.header(title));
}
// Tests run: 3, Failures: 0, Errors: 0, Skipped: 1

The @ValueSource annotation is one of several argument sources for parameterized tests.

10.4. How Do We Migrate JUnit 4 Assume to JUnit 5?

We replace org.junit.Assume with org.junit.jupiter.api.Assumptions and move the message to the last argument. JUnit 4 takes the message first, as in Assume.assumeTrue(String message, boolean b), whereas JUnit 5 takes the condition first. JUnit 4 throws AssumptionViolatedException, whereas JUnit 5 throws TestAbortedException. JUnit 5 has no assumeNotNull() or assumeThat(), so we write the check as a boolean, for example assumeTrue(value != null).

11. Conclusion

A JUnit assumption checks whether a test can run in the current environment. When assumeTrue() or assumeFalse() fails, the method throws TestAbortedException, and Maven and the IDEs report the test as skipped instead of failed. The assumingThat() method skips only one block, and abort() ends the test from any branch or expression. An assumption in @BeforeEach aborts each test, and one in @BeforeAll aborts the whole class. For simple checks of the OS, the JRE, an environment variable or a system property, the conditional annotations do the same job before the test starts.

12. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. Good article, thanks for the information. Also it would have been useful if parameterized tests was also discussed.

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.