How do I use conditional test execution in JUnit?

JUnit 5 provides several ways to conditionally execute tests based on various runtime conditions. This is useful when tests should only run under specific circumstances — like a particular operating system, JRE version, environment variable, or system property.

1. Operating System Conditions

Use @EnabledOnOs and @DisabledOnOs to run tests only on specific operating systems.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;

class OsConditionalTest {

    @Test
    @EnabledOnOs(OS.WINDOWS)
    void onlyOnWindows() {
        // Runs only on Windows
    }

    @Test
    @EnabledOnOs({OS.LINUX, OS.MAC})
    void onlyOnLinuxOrMac() {
        // Runs only on Linux or macOS
    }

    @Test
    @DisabledOnOs(OS.WINDOWS)
    void notOnWindows() {
        // Skipped on Windows
    }
}

2. JRE Version Conditions

Use @EnabledOnJre, @DisabledOnJre, or @EnabledForJreRange to control tests based on the Java version.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;

class JreConditionalTest {

    @Test
    @EnabledOnJre(JRE.JAVA_21)
    void onlyOnJava21() {
        // Runs only on Java 21
    }

    @Test
    @EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_25)
    void betweenJava17AndJava25() {
        // Runs on Java 17 through 25
    }
}

3. System Property Conditions

Enable or disable tests based on JVM system properties.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;

class SystemPropertyTest {

    @Test
    @EnabledIfSystemProperty(named = "env", matches = "ci")
    void onlyInCiEnvironment() {
        // Runs only when -Denv=ci
    }

    @Test
    @DisabledIfSystemProperty(named = "os.arch", matches = ".*32.*")
    void notOn32BitArch() {
        // Skipped on 32-bit architectures
    }
}

4. Environment Variable Conditions

Similar to system properties, but for OS-level environment variables.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;

class EnvVarTest {

    @Test
    @EnabledIfEnvironmentVariable(named = "CI", matches = "true")
    void onlyInCi() {
        // Runs only when CI=true in environment
    }

    @Test
    @DisabledIfEnvironmentVariable(named = "ENV", matches = "prod")
    void skipInProduction() {
        // Skipped when ENV=prod
    }
}

5. Custom Conditions with @EnabledIf and @DisabledIf

For more complex logic, delegate to a static method that returns a boolean.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;

class CustomConditionTest {

    @Test
    @EnabledIf("isDatabaseAvailable")
    void runOnlyIfDatabaseIsUp() {
        // Runs only if the referenced method returns true
    }

    static boolean isDatabaseAvailable() {
        // Perform a real check (ping DB, check port, etc.)
        return "true".equalsIgnoreCase(System.getenv("DB_UP"));
    }
}

The method must:

  • Be static (unless the test class is @TestInstance(PER_CLASS))
  • Return boolean
  • Take no arguments (or accept ExtensionContext)

6. Programmatic Conditions with Assumptions

Sometimes you don’t want a test skipped by annotation but rather aborted mid-execution based on runtime state. Use Assumptions:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumingThat;

class AssumptionTest {

    @Test
    void runOnlyIfOnDeveloperMachine() {
        assumeTrue("DEV".equals(System.getenv("PROFILE")),
                   "Skipping: not on developer profile");
        // rest of test logic
    }

    @Test
    void partiallyConditional() {
        assumingThat("CI".equals(System.getenv("PROFILE")), () -> {
            // Only executed in CI, but the outer test still runs
        });
        // Always executed
    }
}

Difference:

  • @Disabled* / @Enabled* → skipped at discovery time (test shows as skipped).
  • Assumptions → aborted at execution time (test shows as aborted).

Which One Should You Use?

Scenario Recommended Approach
Skip based on OS / JRE @EnabledOnOs, @EnabledOnJre
Skip based on env variable or system property @EnabledIfEnvironmentVariable, @EnabledIfSystemProperty
Complex, dynamic condition @EnabledIf("methodName")
Runtime state check inside the test Assumptions.assumeTrue(...)

Key Takeaways

  • Use annotation-based conditions for static, predictable rules (OS, JRE, env).
  • Use @EnabledIf / @DisabledIf for custom logic that can’t be expressed with the built-in annotations.
  • Use Assumptions when you need to abort a test at runtime based on data available only during execution.
  • Always provide a reason/message (e.g., disabledReason = "...") — future you (and your teammates) will thank you when reading test reports.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.