Which artifact provides the engine that executes classes annotated with JUnit 5's @Suite, and what must such a class satisfy to be discovered and run?
answer
- API artifact = annotations; suite-engine artifact = the TestEngine
- Engine id: junit-platform-suite; aggregator junit-platform-suite pulls both
- Class: non-private, non-abstract, no-arg ctor, static if nested, >=1 selector
- @Suite is @Testable — IDE run gutter
- Suite = nested Launcher run → per-suite config, cross-engine selection
basics
~20 sjunit-platform-suite-engine provides the TestEngine that finds @Suite classes; junit-platform-suite-api provides the annotations (the junit-platform-suite aggregator pulls in both). The class must be non-private, non-abstract, have a no-arg constructor, be static if nested, and declare at least one selector.
solid answer
~50 sTwo artifacts are involved: - **`junit-platform-suite-api`** — the annotations (`@Suite`, `@SelectClasses`, `@IncludeTags`, `@ConfigurationParameter`, …). Compile-time only. - **`junit-platform-suite-engine`** — a `TestEngine` implementation, id `junit-platform-suite`, that discovers `@Suite` classes at runtime and executes them. Needed on the **test runtime** classpath. The aggregator `junit-platform-suite` depends on both, which is why it is the least surprising thing to depend on. For a suite class to be discovered it must be non-private, non-abstract, have a usable no-arg constructor, and be `static` if it is a nested class. It must declare **at least one selector** — a bare `@Suite` with only filters selects nothing. What the engine then does is the important part: for each suite class it builds a `LauncherDiscoveryRequest` from the selectors, filters and `@ConfigurationParameter`s and runs a **nested launcher run**. That is why a suite can carry its own configuration and why reports show the suite as a container node above the selected classes.
go deeper
Know that suites need a dedicated dependency and that the suite class holds annotations, not tests.
Separate the API artifact from the engine artifact and list the class requirements.
Explain the nested-Launcher execution model and what it implies: per-suite configuration, cross-engine selection, report shape, double execution.
Reason about the engine SPI itself — where suites sit relative to other engines, and whether a code-versioned suite or launch-time filtering is the right control surface for the organisation.
## The layering JUnit 5 is three things: the **Platform** (discovery and execution infrastructure, plus the `TestEngine` SPI), **Jupiter** (the `@Test` programming model plus its engine), and **Vintage** (an engine that runs JUnit 3/4 tests). Anything that runs tests does so through the Platform's `Launcher`, which asks every registered `TestEngine` to discover tests for a given request and then to execute what it found. Suites plug into exactly that SPI. `junit-platform-suite-engine` registers a `TestEngine` with the id `junit-platform-suite`. Its discovery step looks for classes annotated `@Suite`; its execution step, for each such class, constructs a fresh discovery request from the class's annotations and runs it through a nested `Launcher`. ## Artifacts | Artifact | Contains | Scope | |---|---|---| | `junit-platform-suite-api` | the `@Suite`/selector/filter annotations | compile/test-compile | | `junit-platform-suite-engine` | the `TestEngine` that runs them | test runtime | | `junit-platform-suite` | aggregator of the two | test | | `junit-platform-suite-commons` | shared internals used by the engine | transitive | A classic failure is having only the API artifact: everything compiles, the IDE shows the annotations, and the suite quietly contributes no tests because no engine claims it. Depending on the aggregator avoids the whole class of problem. Note that Jupiter's own engine is still required for the *selected* tests: the suite engine runs the nested launch, but Jupiter is what actually executes `@Test` methods, and Vintage would be needed for any JUnit 4 classes the suite selects. ## Requirements on the class - Not `private`, not `abstract`. - A no-arg constructor (the implicit default one is fine; the class is instantiated only as a container). - If declared inside another class, it must be `static` — a non-static nested class cannot be instantiated standalone. - At least one selector annotation. Filters alone (`@IncludeTags`, `@IncludeClassNamePatterns`) narrow a selection; they do not create one. - It should contain no `@Test` methods. Any it does contain are not run as part of the suite semantics; the class is a descriptor, not a test class. `@Suite` is meta-annotated with `@Testable`, which is the marker IDEs and tools use to offer a "run" affordance for a non-`@Test` element. That is why IntelliJ or Eclipse shows a run gutter next to a suite class. ## Consequences of the nested-launch design 1. **Per-suite configuration.** The nested discovery request carries its own configuration parameters, so `@ConfigurationParameter` on the suite applies to that run and nothing else. 2. **Cross-engine selection.** Because the nested run goes through the Launcher, the suite can select tests belonging to *any* engine present — Jupiter, Vintage, Cucumber's engine, a custom one — and can restrict them with `@IncludeEngines`/`@ExcludeEngines`. 3. **Report shape.** The suite appears as a container with the selected classes nested under it. Aggregating tools sometimes double-count when the same classes also ran outside the suite. 4. **Double execution.** A suite does not remove its selected classes from ordinary discovery. If both the suite and the normal run happen in the same execution, each selected test runs twice. Keeping suites and the default run disjoint — by naming convention, by tag, or by only launching suites — is a deliberate decision, not a default. 5. **No recursion into itself.** A suite selecting a package that contains the suite class itself would nest infinitely; the engine guards against a suite selecting itself (cycles are detected and reported as a failure), but it is still a smell worth avoiding by keeping suite classes in a package the suite does not scan. ## How you would verify a suite is actually the thing running Run with the platform's engine-level reporting or an execution listener and look at the unique IDs: nodes under a suite start with `[engine:junit-platform-suite]/[suite:...]` and then contain a nested `[engine:junit-jupiter]` segment. Seeing that nested engine segment is the definitive proof the suite engine — not plain Jupiter discovery — produced the run.
- Can a suite select tests that are not Jupiter tests?Yes. The nested run goes through the Platform Launcher, so any registered engine can contribute — Vintage for JUnit 4 classes, Cucumber's engine, a custom engine. @IncludeEngines and @ExcludeEngines let the suite restrict which engines participate, which is a clean way to build, say, a Jupiter-only suite in a mixed corpus.
- What happens if a suite selects a package that contains the suite class itself?That is a cycle: running the suite would discover the suite again. The suite engine detects such cycles and reports a failure rather than recursing forever. The practical habit is to keep suite classes outside the packages they scan, or to exclude them by class-name pattern.
saying these in an interview costs you the question
- Depending only on junit-platform-suite-api and expecting the suite to run
- Thinking Jupiter's engine discovers @Suite classes
- Assuming the selected classes stop running in the ordinary test run once a suite selects them
- Believing a suite can only select Jupiter tests
- Putting @Test methods in the suite class and expecting them to execute as part of it