A teammate imports the JUnit BOM and declares versionless JUnit dependencies, but tests still won't run. Walk through what the BOM does and does NOT do, and the other pieces that must be present for JUnit Platform tests to execute under Gradle.
answer
- BOM = versions only, no jars, no behavior
- need useJUnitPlatform() on test task
- need jupiter ENGINE not just api
- need launcher (auto in Gradle 8.x)
- zero tests -> missing engine or platform switch
basics
~20 sThe BOM only aligns versions; it doesn't make tests run. You still need useJUnitPlatform() on the test task, the Jupiter engine on the runtime classpath, and a launcher. The BOM just guarantees those are mutually compatible.
solid answer
~50 sIt's a common misconception that importing `junit-bom` 'sets up JUnit'. The BOM is purely a version-alignment device — it contributes constraints so api/engine/launcher match. It adds no jars and changes no task behavior. For tests to actually execute you need three more things: (1) `tasks.test { useJUnitPlatform() }` so Gradle's `Test` task uses the Platform runner instead of the default JUnit 4 runner; (2) the **Jupiter engine** on `testRuntimeClasspath` — the `junit-jupiter` aggregator includes it, but if you only added `junit-jupiter-api` you'll compile yet discover no engine and run nothing; (3) a `junit-platform-launcher` (auto-added in Gradle 8.x, or declared `testRuntimeOnly`). The BOM makes those three coherent in version, but their *presence* and the `useJUnitPlatform()` switch are separate responsibilities. So 'tests won't run' usually means a missing engine or a missing `useJUnitPlatform()`, not a BOM problem.
code
kotlin · 7 linesdependencies {
testImplementation(platform("org.junit:junit-bom:5.10.2")) // alignment only
testImplementation("org.junit.jupiter:junit-jupiter") // api + engine + params
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.test { useJUnitPlatform() } // the actual switch that runs JUnit 5go deeper
Know the BOM only aligns versions; you still call useJUnitPlatform() and need the engine.
Enumerate the three required pieces and diagnose 'zero tests' as missing engine or platform switch.
Distinguish version-alignment failures (BOM territory) from presence/configuration failures, and read the runtime classpath to confirm.
Bake useJUnitPlatform() + BOM + engine + launcher into a convention plugin so modules can't misconfigure it.
## What the BOM is responsible for The `org.junit:junit-bom` BOM, imported via `platform(...)`, supplies **version constraints** so the JUnit modules that end up on the classpath all use a mutually-tested version. That is its entire job. It does not: - add any jar to the classpath, - enable the JUnit Platform on the `Test` task, - choose between Jupiter and Vintage, - guarantee an engine is present. ## The three things you still need ### 1. `useJUnitPlatform()` on the test task Gradle's `Test` task defaults to the old JUnit 4 runner. To run JUnit 5 you must opt in: ```kotlin tasks.test { useJUnitPlatform() } ``` Without it, Jupiter tests are simply not discovered (you'll see zero Jupiter tests run). ### 2. An engine on testRuntimeClasspath The launcher needs at least one `TestEngine`. For JUnit 5 that's `junit-jupiter-engine`. The convenient aggregator `org.junit.jupiter:junit-jupiter` pulls `api + engine + params`. If you declared only `junit-jupiter-api` (enough to compile), there's no engine at runtime and nothing is discovered: ```kotlin testImplementation("org.junit.jupiter:junit-jupiter") // api + engine + params ``` ### 3. A launcher `junit-platform-launcher` is what Gradle invokes to drive engines. Gradle 8.x auto-adds a matching one; on older setups or when a transitive interferes, declare it: ```kotlin testRuntimeOnly("org.junit.platform:junit-platform-launcher") ``` ## Putting it together ```kotlin dependencies { testImplementation(platform("org.junit:junit-bom:5.10.2")) // alignment only testImplementation("org.junit.jupiter:junit-jupiter") // api + engine testRuntimeOnly("org.junit.platform:junit-platform-launcher") } tasks.test { useJUnitPlatform() } ``` ## Diagnosing 'tests won't run' - **Zero tests, no error** -> likely missing `useJUnitPlatform()` or missing engine. Check the test task config and `./gradlew dependencies --configuration testRuntimeClasspath` for `junit-jupiter-engine`. - **Runtime LinkageError from org.junit.platform** -> a *version* mismatch; that's where the BOM helps. - **'no tests found for given includes'** -> engine couldn't initialize or no class matched discovery. The key mental model: **BOM = which versions; useJUnitPlatform + engine + launcher = whether and how tests run.** They are orthogonal, and confusing them is the usual cause of 'I added the BOM but nothing runs'.
- Why might tests compile but report 'zero tests' even with the BOM applied?Most often either useJUnitPlatform() is missing on the test task (Gradle defaults to the JUnit 4 runner) or only junit-jupiter-api is present, so there's no junit-jupiter-engine on the runtime classpath to discover the tests.
- Does the BOM choose between the Jupiter and Vintage engines?No. The BOM only aligns versions. Which engines run depends on which engine jars are on testRuntimeClasspath (junit-jupiter-engine and/or junit-vintage-engine). The launcher runs whatever engines it discovers.
saying these in an interview costs you the question
- Believing importing the BOM enables JUnit 5 / sets useJUnitPlatform().
- Declaring only junit-jupiter-api and expecting tests to run (no engine at runtime).
- Assuming a BOM problem when the real cause is a missing engine or platform switch.