A JUnit 5 test only makes sense on Linux and only when the system property db.url is set. Compare marking it unconditionally @Disabled with using JUnit 5's conditional annotations such as @EnabledOnOs and @EnabledIfSystemProperty — which do you choose, and why does it matter?
answer
- @Disabled = never, anywhere; conditions = only where meaningful
- org.junit.jupiter.api.condition family: OS, JRE, sysprop, env var, @EnabledIf
- conditions are declarative and evaluated before setup
- assumptions abort mid-test, after @BeforeEach
- condition satisfied nowhere = silent zero coverage
basics
~20 sUse the conditional annotations. @Disabled is unconditional: the test never runs anywhere, so you lose the signal even on machines where it would work. @EnabledOnOs(LINUX) and @EnabledIfSystemProperty run it exactly where its prerequisites hold and report it as skipped elsewhere, with the condition as the reason.
solid answer
~50 s`@Disabled` means *never, everywhere*. It is the right tool only when the test cannot pass anywhere right now — the feature is unbuilt, the test is broken, the behaviour is under rework. This case is different: the test **can** pass, just not everywhere. That is what JUnit 5's conditional annotations exist for: ```java @Test @EnabledOnOs(OS.LINUX) @EnabledIfSystemProperty(named = "db.url", matches = ".+") void replicatesViaUnixSocket() { ... } ``` On a Linux CI agent with the property set it runs and can fail the build; on a developer's Mac it is skipped with a reason naming the condition. `@Disabled` would silently throw away the Linux coverage too. Jupiter ships a family of these: `@EnabledOnOs`/`@DisabledOnOs`, `@EnabledOnJre`/`@EnabledForJreRange`, `@EnabledIfSystemProperty`, `@EnabledIfEnvironmentVariable`, `@EnabledIf` with a custom method. All are ordinary `ExecutionCondition` extensions, so a condition you write yourself plugs in the same way.
code
java · 12 lines@Test
@EnabledOnOs(OS.LINUX)
@EnabledIfSystemProperty(named = "db.url", matches = ".+")
void replicatesViaUnixSocket() {
// runs on Linux with db.url set; skipped elsewhere with a reason
}
@Test
@Disabled("refund flow not implemented yet - PROJ-88")
void refundsWholeOrder() {
// cannot pass anywhere today
}go deeper
Know that JUnit 5 has conditional annotations and that @Disabled means never, everywhere.
Name the condition family, show them combining, and state that they are evaluated before the test instance is created.
Add the judgement rule — can this pass somewhere today? — and warn that a condition nothing satisfies is silent zero coverage.
Treat environment-dependence as pipeline design: which agent satisfies which condition, and how skip counts are monitored so conditional coverage is real.
## Two different statements `@Disabled` and the conditional annotations produce the same *outcome* — a skipped test — but they encode different claims: - `@Disabled("...")` says: **this should not run at all, right now, anywhere.** The reason is a note to humans, and the intent is that someone removes it later. - `@EnabledOnOs(OS.LINUX)` says: **this is only meaningful under these conditions**, which is a permanent, machine-checked property of the test. Using `@Disabled` for the second case is the common mistake, and it costs real coverage: the test never runs even where the prerequisites are satisfied. It also hides the true dependency — a future reader sees "disabled, see ticket" and has no idea the test works fine on CI. ## The built-in conditional family Jupiter provides, in `org.junit.jupiter.api.condition`: - **Operating system:** `@EnabledOnOs` / `@DisabledOnOs` (with `OS.LINUX`, `OS.MAC`, `OS.WINDOWS`, …). - **Java runtime:** `@EnabledOnJre`, `@DisabledOnJre`, `@EnabledForJreRange`, `@DisabledForJreRange`. - **System properties:** `@EnabledIfSystemProperty(named = …, matches = <regex>)` and its disabled twin. - **Environment variables:** `@EnabledIfEnvironmentVariable` / `@DisabledIfEnvironmentVariable`. - **Arbitrary logic:** `@EnabledIf` / `@DisabledIf` naming a boolean method. They apply to methods and to classes, they compose (multiple conditions must all pass), and their reasons appear in the report, so a skip explains itself: *"Disabled on operating system: Mac OS X"*. ## And what about assumptions? `Assumptions.assumeTrue(...)` inside the body also results in a skip, but it aborts *during* execution: the class is instantiated and `@BeforeEach` has already run. Use it when the precondition can only be determined after setup — for example after connecting and discovering the server's version. When the precondition is known before the test starts, a condition annotation is better: it is declarative, visible without reading the body, and avoids paying for setup. ## Choosing Ask: *could this test pass somewhere, today?* - **No** — the feature is not implemented, the test is wrong, behaviour is being rewritten → `@Disabled` with a reason and a ticket. It is temporary and should be tracked. - **Yes, under stated prerequisites** → a conditional annotation, or a custom `ExecutionCondition` if the rule is domain-specific ("only when the staging service is reachable"). - **Yes, but only discoverable mid-test** → an assumption. ## A caveat about conditional skipping Conditions make skips invisible in a good way and in a bad way. If your only CI agent is Windows, `@EnabledOnOs(OS.LINUX)` means the test never runs at all and nobody notices, because a green build reports it as an expected skip. Whichever mechanism you use, someone has to look at skip counts periodically. Conditions describe *where* a test is meaningful; they do not guarantee that anywhere in your pipeline actually satisfies them.
- When is Assumptions.assumeTrue a better fit than a conditional annotation?When the precondition can only be evaluated after setup has run — for example after connecting to a broker and reading its version, or inspecting data you just loaded. Annotations are evaluated before the test instance exists, so they cannot see that state. The tradeoff is that an assumption hides the condition inside the body and still pays for @BeforeEach.
- Both @Disabled and @EnabledOnOs produce a skip. Does the report distinguish them?Yes, through the reason. @Disabled reports your string (or a generic default), while a condition reports its own message such as "Disabled on operating system: Windows 11". Both count as skipped, so tooling that only counts skips treats them alike — which is why reasons, and periodic review of skip counts, matter.
saying these in an interview costs you the question
- Using @Disabled for OS- or environment-specific tests and calling it equivalent
- Claiming conditional annotations make the test fail rather than skip when unsatisfied
- Thinking assumptions and condition annotations are evaluated at the same point in the lifecycle
- Assuming a green build means no coverage was silently skipped