The short answer. Nothing you wrote with@EnabledOnOs,@EnabledOnJre,@EnabledIfSystemProperty,@EnabledIfEnvironmentVariableor a customExecutionConditionneeds to change for JUnit 6. Compared with the last JUnit 5 release, the public API of that package is identical except that theJREenum gainsJAVA_27andJAVA_28. What does change is the floor: JUnit 6’s class files are Java 17, so a JUnit 6 test can never run on Java 8, and the 2025 article’s@EnabledOnJre(JRE.JAVA_8)example now marks a test that cannot be enabled. For the assumptions half of this topic, see JUnit 6 Assumptions and Conditional Test Execution Guide.
Versions, and honest limits. JUnit Jupiter 6.1.3, junit-platform-launcher 6.1.3, AssertJ 3.27.7, Maven Surefire 3.6.0, JDK 25.0.4.1 (Temurin), on Linux amd64. The code is theconditionalmodule of asmhatre/JUnit_Tutorials, and every console block is quoted from a file underconditional/output/. Limits. I could only observe one machine: Linux on amd64 with Java 25. When the article says what happens on Windows, macOS or another Java version, it is reasoning from the annotations, not from a run, and it says so. The run that shows the weekend condition depends on the day it ran. Each test is started through the JUnit Platform launcher so the skip reason can be read; a normalmvn testprints the same decisions in its own format.
What a skipped test looks like, and where the decision happens
@BeforeEach. Section 6 proves it with 06-skipped-versus-aborted.txt.
Why not just @Disabled?
In the high-speed world of modern DevOps and CI/CD, the “one size fits all” philosophy is a relic of the past. As architectures move toward microservices and multi-cloud environments, your testing strategy must adapt. You might have a test suite that interacts with a Windows-specific DLL, a legacy integration that requires Java 8, or a performance benchmark that only makes sense on high-spec production-like runners. Running these tests in the wrong environment doesn’t just lead to confusing failure logs; it creates “noise,” increases build times, and erodes trust in your automation. This is where JUnit 5 Conditional Execution shines. Unlike the blunt@Disabled annotation, JUnit 5 (Jupiter) provides a sophisticated toolkit to programmatically enable or disable tests based on the runtime context.
In this comprehensive guide, we’ll explore the standard annotations and dive into advanced custom conditions to make your test suite truly environment-aware.
The words above are the 2025 opening, unchanged. The first paragraph’s example of “a legacy integration that requires Java 8” is the one that no longer works with JUnit 6, as section 2 shows.
1. Operating System Conditions: Target Your Infrastructure
Operating systems handle file systems, networking stacks, and native libraries differently. If your code uses Runtime.exec() or accesses specific paths like C:\Windows\System32, running that test on a Linux build server is a guaranteed failure.
@EnabledOnOs & @DisabledOnOs
These annotations use the OS enum to filter execution. You can target a single OS or a combination.
package line added, and the published block is kept verbatim in OsConditionalTest.java):
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.*;
class OsConditionalTest {
@Test
@EnabledOnOs(OS.WINDOWS)
void testWindowsRegistryAccess() {
System.out.println("Executing Windows-specific logic.");
}
@Test
@EnabledOnOs({OS.LINUX, OS.MAC})
void testUnixSocketBehavior() {
System.out.println("Executing Unix-specific logic.");
}
@Test
@DisabledOnOs(OS.SOLARIS)
void runEverywhereButSolaris() {
System.out.println("Solaris is not supported for this module.");
}
}
Run on this machine, from 01-os-and-jre.txt:
This machine: os.name=Linux, os.arch=amd64, java.version=25.0.4.1
OS.current()=LINUX, JRE.currentVersion()=JAVA_25
OsConditionalTest
runEverywhereButSolaris() : PASSED
testUnixSocketBehavior() : PASSED
testWindowsRegistryAccess() : SKIPPED (Disabled on operating system: Linux)
The skip reason is Disabled on operating system: Linux. On a Mac I would expect the same wording with the Mac’s name in place of “Linux” (the text is built from the current OS); I did not run that.
The 2025 expected output for this section, with a verdict
Execution Output (Running on macOS):
testWindowsRegistryAccess: Skipped (Reason: Disabled on Mac OS X)testUnixSocketBehavior: Passed (Output: Executing Unix-specific logic.)runEverywhereButSolaris: Passed (Output: Solaris is not supported for this module.)
Verdict: the pass/skip pattern is right for a Mac. The reason text Disabled on Mac OS X is not what the run above prints for Linux (Disabled on operating system: Linux), and I have not seen JUnit 6.1.3 print the 2025 wording anywhere. The Output: parts are what the tests print with System.out.println; the module’s run printed the same lines.
Going deeper: CPU architecture, which the 2025 article did not mention
@EnabledOnOs and @DisabledOnOs also take an architectures attribute. The comparison with the old jar shows it is not new in 6 (09-api-5-vs-6.txt); it simply was not in the 2025 article. From ArchitectureConditionalTest.java:
@Test
@EnabledOnOs(value = OS.LINUX, architectures = "aarch64")
void onlyArmLinux() {
}
@Test
@EnabledOnOs(value = OS.LINUX, architectures = {"x86_64", "amd64"})
void onlyIntelLinux() {
}
@Test
@DisabledOnOs(architectures = "aarch64")
void anythingButArm() {
}
and the run, from 02-architecture.txt:
ArchitectureConditionalTest
anythingButArm() : PASSED
onlyArmLinux() : SKIPPED (Disabled on operating system: Linux (amd64))
onlyIntelLinux() : PASSED
Note that this machine reports its architecture as amd64, not x86_64, so list every spelling you care about (as the example does) rather than assuming one.
2. Java Runtime Conditions: JRE Version Control
With Java’s six-month release cycle, maintaining backward compatibility while adopting modern features like Records (Java 16) or Virtual Threads (Java 21) is a common challenge.
@EnabledOnJre & @EnabledForJreRange
Instead of wrapping your test logic in if (javaVersion > 11) blocks, use declarative annotations.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.*;
class JreConditionalTest {
@Test
@EnabledOnJre(JRE.JAVA_8)
void testLegacyCompatibility() {
System.out.println("Running on legacy Java 8 environment.");
}
@Test
@EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_21)
void testLtsFeatureSet() {
System.out.println("Running on a modern LTS version.");
}
@Test
@DisabledOnJre(JRE.OTHER)
void hideFromExperimentalVersions() {
System.out.println("Running on a stable, recognized JRE.");
}
}
On Java 25, from 01-os-and-jre.txt:
JreConditionalTest
hideFromExperimentalVersions() : PASSED
testLegacyCompatibility() : SKIPPED (Disabled on JRE version: 25.0.4.1)
testLtsFeatureSet() : SKIPPED (Disabled on JRE version: 25.0.4.1)
Two things differ from what the 2025 text implied. First, testLtsFeatureSet is skipped here because the range is Java 17 to 21 and this machine runs 25; the old text only showed it passing on 21. Second, the reason carries the full java.version string, 25.0.4.1, not just the major number the old output showed.
The Java 8 example cannot work any more. JRE.JAVA_8 still exists in the enum, so the code compiles, but JUnit 6’s own classes are compiled for Java 17. From 09-api-5-vs-6.txt:
class file major version of EnabledOnOs in 5.14.4: 52 (52 = Java 8, 61 = Java 17)
class file major version of EnabledOnOs in 6.1.3: 61 (52 = Java 8, 61 = Java 17)
A JVM older than 17 cannot load class files of version 61, so no JUnit 6 run can ever be on Java 8 and @EnabledOnJre(JRE.JAVA_8) never enables its test. If you must still test on Java 8 you are on JUnit 5, and these annotations behave the same there. (That Java 8 is the floor of JUnit 5 comes from the class-file version 52 above, not from the release notes.)
The 2025 expected output for this section, with a verdict
Execution Output (Running on Java 21):
testLegacyCompatibility: Skipped (Reason: Disabled on JRE version: 21)testLtsFeatureSet: Passed (Output: Running on a modern LTS version.)hideFromExperimentalVersions: Passed
Verdict: plausible for Java 21 (testLtsFeatureSet would pass in the 17 to 21 range), but I only ran Java 25, so I cannot confirm it; the reason text for testLegacyCompatibility would carry the full version string, not 21. hideFromExperimentalVersions passes on Java 25 (see the run above) because 25 has its own constant and so is not JRE.OTHER; 6.1.3 lists versions up to JAVA_28.
3. System Property Conditions: JVM Level Control
System properties (passed via -Dname=value) are perfect for toggling “Heavy” or “Experimental” tests without altering the source code.
@EnabledIfSystemProperty
This annotation is incredibly flexible because it supports Regular Expressions in the matches attribute.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
class SystemPropertyTest {
@Test
@EnabledIfSystemProperty(named = "test.severity", matches = "critical|high")
void runOnlyHighSeverityTests() {
System.out.println("Running a high-priority test case.");
}
@Test
@EnabledIfSystemProperty(named = "user.language", matches = "en")
void testEnglishLocalization() {
System.out.println("Testing English locale specific logic.");
}
}
The test starts a fresh JVM with the flags I choose and reports each decision (ConditionalTest.java). From 03-system-properties.txt:
SystemPropertyTest in a fresh JVM, three times.
No -D flags:
runOnlyHighSeverityTests() : SKIPPED (System property [test.severity] does not exist)
testEnglishLocalization() : PASSED
-Dtest.severity=high -Duser.language=fr (the command line from the 2025 article):
runOnlyHighSeverityTests() : PASSED
testEnglishLocalization() : SKIPPED (System property [user.language] with value [fr] does not match regular expression [en])
-Dtest.severity=low -Duser.language=en:
runOnlyHighSeverityTests() : SKIPPED (System property [test.severity] with value [low] does not match regular expression [critical|high])
testEnglishLocalization() : PASSED
The middle block is the command line from the 2025 article, and its skip message matches what that article printed word for word. The first block shows what the article did not: with no flag at all the message is System property [test.severity] does not exist. The pattern is a regular expression, as the message says.
The 2025 expected output for this section, with a verdict
Execution Output (Command: mvn test -Dtest.severity=high -Duser.language=fr):
runOnlyHighSeverityTests: Passed (Output: Running a high-priority test case.)testEnglishLocalization: Skipped (Reason: System property [user.language] with value [fr] does not match regular expression [en])
Verdict: matches the middle block above. One caution: with Surefire, a -D flag on the Maven command line reaches the test JVM only through Surefire’s own forwarding of user properties; I started the JVM directly here, so I have not shown the Maven path.
4. Environment Variables: The CI/CD Standard
In modern cloud-native development, environment variables are the standard for configuration. Whether you are using Docker, Kubernetes, or GitHub Actions, variables like STAGE, DB_URL, or CI are readily available.
@EnabledIfEnvironmentVariable
This is the most common way to separate “Local Dev” tests from “CI Pipeline” tests.
Code unchanged (PipelineTest.java, original in PipelineTest.java):
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
class PipelineTest {
@Test
@EnabledIfEnvironmentVariable(named = "CI", matches = "true")
void performanceBenchmark() {
System.out.println("Benchmark running on dedicated CI worker.");
}
@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "STAGING")
void integrationWithExternalAPI() {
System.out.println("Connecting to Staging API...");
}
}
A test cannot change its own JVM’s environment, so each case runs in a fresh JVM whose environment the test sets. From 04-environment-variables.txt:
PipelineTest in a fresh JVM, three times, with the environment set by the test.
CI unset, ENV=DEV (a developer laptop):
integrationWithExternalAPI() : SKIPPED (Environment variable [ENV] with value [DEV] does not match regular expression [STAGING])
performanceBenchmark() : SKIPPED (Environment variable [CI] does not exist)
CI=true, ENV=STAGING (a pipeline):
integrationWithExternalAPI() : PASSED
performanceBenchmark() : PASSED
CI=false, ENV unset:
integrationWithExternalAPI() : SKIPPED (Environment variable [ENV] does not exist)
performanceBenchmark() : SKIPPED (Environment variable [CI] with value [false] does not match regular expression [true])
Look at the last block. CI=false is a CI variable that exists but does not match true, and the reason says so differently from the unset case. Many pipelines set CI=true and nothing else, so the ENV test is skipped there unless ENV=STAGING is also set, which is easy to forget.
The 2025 expected output for this section, with a verdict
Execution Output (Running Locally where CI is unset and ENV=DEV):
performanceBenchmark: Skipped (Reason: Environment variable [CI] does not exist)integrationWithExternalAPI: Skipped (Reason: Environment variable [ENV] with value [DEV] does not match [STAGING])
Verdict: the pass/skip pattern is right, but the wording is not: JUnit 6.1.3 says does not match regular expression [STAGING], where the 2025 text had does not match [STAGING]. The first block above is that exact scenario.
5. Beyond the Basics: Custom Conditions
If the built-in annotations don’t meet your needs, JUnit 5 allows you to create your own logic by implementing the ExecutionCondition interface.
Advanced: Creating a “Day of Week” Condition
The 2025 block, exactly as published (WeekendCondition.java):
import org.junit.jupiter.api.extension.*;
import java.time.DayOfWeek;
import java.time.LocalDate;
public class WeekendCondition implements ExecutionCondition {
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
DayOfWeek today = LocalDate.now().getDayOfWeek();
if (today == DayOfWeek.SATURDAY || today == DayOfWeek.SUNDAY) {
return ConditionEvaluationResult.enabled("It is the weekend!");
}
return ConditionEvaluationResult.disabled("Skipping: Today is " + today);
}
}
// Usage in your test class
class CustomConditionTest {
@Test
@ExtendWith(WeekendCondition.class)
void weeklyBackupStabilityTest() {
System.out.println("Running heavy weekend-only backup test.");
}
}
That block does not compile as published. CustomConditionTest uses @Test but the block imports no org.junit.jupiter.api.Test. From 08-old-code-vs-junit-6.txt, where the five other 2025 blocks all compile:
WeekendCondition: DOES NOT COMPILE
src/old/WeekendCondition.java:19: error: cannot find symbol
symbol: class Test
location: class CustomConditionTest
1 error
With the one import added (WeekendCondition.java) it runs. From 05-custom-condition.txt, run on the day this was written:
The WeekendCondition from the 2025 article, run today (2026-10-11, a SUNDAY):
weeklyBackupStabilityTest() : PASSED
It is a Sunday, so the weekend-only test runs. The 2025 output (“Skipping: Today is WEDNESDAY”) was a different day, and that is the problem with this design: LocalDate.now() is read inside the condition, so the test’s outcome depends on when you run it and you cannot test the condition itself on a Sunday for a Wednesday.
Going deeper: a reusable, testable version
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(DayOfWeekCondition.class)
public @interface EnabledOnDays {
DayOfWeek[] value();
}
public class DayOfWeekCondition implements ExecutionCondition {
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
var annotation = context.getElement().flatMap(e -> AnnotationSupport.findAnnotation(e, EnabledOnDays.class));
if (annotation.isEmpty()) {
return ConditionEvaluationResult.enabled("@EnabledOnDays is not present");
}
DayOfWeek today = context.getConfigurationParameter("demo.today")
.map(DayOfWeek::valueOf)
.orElseGet(() -> LocalDate.now().getDayOfWeek());
DayOfWeek[] days = annotation.get().value();
return Arrays.asList(days).contains(today)
? ConditionEvaluationResult.enabled("Today is " + today)
: ConditionEvaluationResult.disabled("Today is " + today + ", test runs on " + Arrays.toString(days));
}
}
The first thing the condition does is look for its own annotation, and if there is none it returns enabled, so it is harmless when it meets a method without one. The run, with the day pinned from the test (05-custom-condition.txt):
demo.today=SATURDAY
noAnnotationRunsAlways() : PASSED
wednesdayOnly() : SKIPPED (Today is SATURDAY, test runs on [WEDNESDAY])
weekendOnly() : PASSED
demo.today=WEDNESDAY
noAnnotationRunsAlways() : PASSED
wednesdayOnly() : PASSED
weekendOnly() : SKIPPED (Today is WEDNESDAY, test runs on [SATURDAY, SUNDAY])
demo.today=FRIDAY
noAnnotationRunsAlways() : PASSED
wednesdayOnly() : SKIPPED (Today is FRIDAY, test runs on [WEDNESDAY])
weekendOnly() : SKIPPED (Today is FRIDAY, test runs on [SATURDAY, SUNDAY])
The 2025 expected output for this section, with a verdict
Execution Output (Running on a Wednesday):
weeklyBackupStabilityTest: Skipped (Reason: Skipping: Today is WEDNESDAY)
Verdict: reproduced for a Wednesday by the pinned run above (demo.today=WEDNESDAY with the reusable version); the original class could not be run on a Wednesday today without changing the clock.
6. Assumptions vs. Conditions: Which to use?
While @EnabledIf... annotations are static (determined before the test starts), sometimes you need to check a condition dynamically inside the test method. For this, use Assumptions.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assumptions.*;
class AssumptionTest {
@Test
void testDatabaseConnection() {
boolean isDbUp = false; // Mocking a down database
assumeTrue(isDbUp, "Aborting: Database is not reachable.");
System.out.println("This will not print because the assumption fails.");
}
}
“Static” is a good first approximation, and here is the precise version. A condition is decided before the test is set up; an assumption is a statement inside a test that has already been set up. The module runs one test of each kind next to an ordinary one and records every callback (LifecycleConditionalTest.java). From 06-skipped-versus-aborted.txt:
Results
a_runs() : PASSED
b_skippedByAnnotation() : SKIPPED (Disabled on operating system: Linux)
c_abortedByAssumption() : ABORTED (Assumption failed: database is down)
Every callback and test body that ran, in order
@BeforeEach a_runs
body a_runs
@AfterEach a_runs
@BeforeEach c_abortedByAssumption
@AfterEach c_abortedByAssumption
The AssumptionTest from the 2025 article, unchanged
testDatabaseConnection() : ABORTED (Assumption failed: Aborting: Database is not reachable.)
The test skipped by the annotation never reaches @BeforeEach or @AfterEach. The test aborted by the assumption already ran @BeforeEach (and gets @AfterEach), and then stopped before its body. If your setup is expensive, such as starting a container, a condition avoids it and an assumption pays for it first. The last block is the 2025 article’s own AssumptionTest; its result and message match the old text.
The 2025 expected output for this section, with a verdict
Execution Output:
testDatabaseConnection: Aborted (Reason: Assumption failed: Aborting: Database is not reachable.)
Verdict: matches the last block above. For the assumptions API itself, assumeTrue, assumingThat and the rest, see the assumptions article.
What the 2025 article left out: reasons, stacking, class level and an off switch
A few behaviours you will meet quickly. All are in StackingConditionalTest.java and ClassLevelConditionalTest.java, and the output is 07-stacking-and-switches.txt. Several conditions on one test. All of them must allow the test. When more than one is false, the report names one reason, not all: @Test
@EnabledOnOs(OS.WINDOWS)
@EnabledIfSystemProperty(named = "never.set", matches = ".*")
void bothConditionsFalse() {
}
/** The first is true on Linux, so the second one decides. */
@Test
@EnabledOnOs(OS.LINUX)
@EnabledIfSystemProperty(named = "never.set", matches = ".*")
void onlySecondConditionFalse() {
}
The run, from 07-stacking-and-switches.txt:
bothConditionsFalse() : SKIPPED (Disabled on operating system: Linux)
onlySecondConditionFalse() : SKIPPED (System property [never.set] does not exist)
On the first test both conditions are false and only the operating system is reported; on the second only the system property is false, so that is the one named. This is what I saw with these two annotations; I did not read JUnit’s source to confirm the rule for every combination.
Your own reason. Every condition annotation has a disabledReason, and @EnabledIf takes the name of a method that returns boolean:
From 07-stacking-and-switches.txt:
methodConditionDefaultReason() : SKIPPED (@EnabledIf("alwaysFalse") evaluated to false)
methodConditionOwnReason() : SKIPPED (feature flag is off)
withOwnReason() : SKIPPED (Disabled on operating system: Linux ==> Needs the Windows registry)
Without a reason of your own, the report for @EnabledIf is the unhelpful evaluated to false. Writing a disabledReason is the cheapest improvement you can make to a skipped test, because that sentence is what the next person sees in the report.
A condition on a class. The class skips as a unit:
From 07-stacking-and-switches.txt:
ClassLevelConditionalTest (@EnabledOnOs(WINDOWS) on the class, two test methods)
ClassLevelConditionalTest : CONTAINER SKIPPED (Disabled on operating system: Linux)
There is one skip event, for the class, not one per method. A tool that counts skipped tests sees fewer skips than there are methods.
Turning conditions off. The configuration parameter junit.jupiter.conditions.deactivate takes a pattern of condition class names. With a pattern covering the condition package, the Windows-only test from section 1 runs on Linux:
From 07-stacking-and-switches.txt:
OsConditionalTest again, with the configuration parameter
junit.jupiter.conditions.deactivate=org.junit.jupiter.api.condition.*
runEverywhereButSolaris() : PASSED
testUnixSocketBehavior() : PASSED
testWindowsRegistryAccess() : PASSED
This is how you run everything once, regardless of environment, for example on a build you suspect of skipping too much. I used it as a configuration parameter on the launcher request; the same key can be set wherever you configure JUnit (for example in junit-platform.properties), though I did not test that route.
JUnit 5 to 6: what actually changed for conditions
To answer that without relying on a changelog, the module downloadsjunit-jupiter-api 5.14.4 (the last 5.x on Maven Central when I looked) and 6.1.3 and compares the package that holds these annotations with javap. From 09-api-5-vs-6.txt (compile-old-snippets.sh):
# org/junit/jupiter/api/condition in junit-jupiter-api 5.14.4 (last JUnit 5 release on Maven Central) and 6.1.3
class file major version of EnabledOnOs in 5.14.4: 52 (52 = Java 8, 61 = Java 17)
class file major version of EnabledOnOs in 6.1.3: 61 (52 = Java 8, 61 = Java 17)
classes in the package: 39 in 5.14.4, 40 in 6.1.3
classes only in 6.1.3: org/junit/jupiter/api/condition/package-info.class
classes only in 5.14.4:
classes with an identical public and protected signature (javap): 37
classes whose signature differs: org.junit.jupiter.api.condition.AbstractJreRangeCondition org.junit.jupiter.api.condition.JRE
diff of javap org.junit.jupiter.api.condition.AbstractJreRangeCondition (5.14.4 -> 6.1.3):
4a5
> static {};
diff of javap org.junit.jupiter.api.condition.JRE (5.14.4 -> 6.1.3):
22a23,24
> public static final org.junit.jupiter.api.condition.JRE JAVA_27;
> public static final org.junit.jupiter.api.condition.JRE JAVA_28;
Of 39 classes present in both, 37 have an identical public and protected signature. The two that differ: JRE gained JAVA_27 and JAVA_28, and an internal helper gained a static initializer. The only new file is a package-info. The reading: JUnit 6 moved the baseline, not the conditions. A test class with these annotations compiles against either.
Why This Matters?
- Lower False Positives: Developers stop ignoring “red” builds because failures are actually relevant.
- Resource Optimization: Don’t waste cloud compute minutes running Mac-specific UI tests on a Linux container.
- Scalable Documentation: Your code becomes self-documenting. A newcomer can see
@EnabledOnOs(OS.LINUX)and instantly know the test’s intent.
Summary of Key Annotations
The original table, with a column for JUnit 6.1.3.| Annotation | Use Case | Best For | JUnit 6.1.3 |
|---|---|---|---|
| @EnabledOnOs | OS Targeting | Native libraries, File Paths | Unchanged; also takes architectures |
| @EnabledOnJre | Version Locking | Records, Sealed Classes, Loom | Unchanged; enum now to JAVA_28; JAVA_8 can never be enabled |
| @EnabledIfSystemProperty | JVM Flags | matches via Regex, Custom Flags | Unchanged; the pattern is a regular expression |
| @EnabledIfEnvironmentVariable | Cloud/CI Config | GitHub Actions, Docker, Jenkins | Unchanged; unset and non-matching are different reasons |
| @ExtendWith | Custom Logic | Time-based or Business Logic checks | Unchanged; prefer a composed annotation and a pinned clock |
Should you even do this?
A fair answer. Use conditions for facts about where the test runs that you cannot change, such as the operating system or a native library. Do not use them to hide a test that is broken or flaky, and do not put the same skip on many tests: then the environment is deciding what your suite means. This is advice, not something the module measures, but there is one measured warning in it: a skipped test is not a failed test, and all the outputs above show builds that stay green while tests silently do not run. Write a disabledReason every time, look at the skipped count in your reports, and have at least one environment in which the important conditional tests are enabled. When a whole group of tests belongs to a stage of the build rather than to an environment, a test tag selected by the build is the clearer tool.
By mastering these tools, you transform your test suite from a static script into a dynamic, intelligent guardian of your code quality.
Further reading
- The code for this article: asmhatre/JUnit_Tutorials, in the
conditionalmodule, with the transcripts underconditional/output/and its README. - JUnit 6 Assumptions and Conditional Test Execution Guide — assumptions, and conditions from the other side.
- JUnit 6 tutorial
- JUnit 6 assertions
- JUnit 6 index
- JUnit User Guide (6.1.3), the section on conditional test execution.
No Comments yet!