skip to content

JUnit 5 test methods may declare parameters even though you never pass arguments yourself, and the test still runs. Which parameter types does JUnit 5 supply out of the box, what is each one for, and how does JUnit know what to inject?

level: juniorimportance: should knowfreq 42%

answer

  1. TestInfo — display name, tags, class, method (Optionals)
  2. TestReporter — publishEntry(key, value) into the report
  3. RepetitionInfo — only inside @RepeatedTest
  4. all injected via ParameterResolver extensions
  5. unsupported parameter → ParameterResolutionException, test never runs

basics

~20 s

JUnit 5 injects parameters through ParameterResolver extensions. Built in: TestInfo (display name, tags, test class/method), TestReporter (publish key/value entries into the report), and RepetitionInfo (current and total repetitions, inside a repeated test). Extensions can register resolvers for their own types.

solid answer

~50 s

Unlike JUnit 4, Jupiter test methods, constructors and lifecycle methods may declare parameters, and each one is supplied by a registered `ParameterResolver` extension. Three resolvers ship with the engine: - **`TestInfo`** — metadata about the current test: `getDisplayName()`, `getTags()`, `getTestClass()`, `getTestMethod()`. Handy for logging or building unique data per test. - **`TestReporter`** — `publishEntry(key, value)` writes a reporting entry that the engine forwards to the launcher, so it lands in the report and the IDE instead of in stdout. - **`RepetitionInfo`** — only valid in a `@RepeatedTest` (and its per-repetition lifecycle methods): `getCurrentRepetition()` and `getTotalRepetitions()`. Other extensions add their own — a parameterized test's arguments and Spring's `SpringExtension` beans arrive through the same mechanism. If no registered resolver supports a declared parameter, the test does not run: JUnit throws `ParameterResolutionException` saying no resolver was registered for it.

code

java · 23 lines
java
class BuiltInInjectionTest {

    @BeforeEach
    void setUp(TestInfo testInfo) {
        // getTestMethod() is present here; it would be empty in @BeforeAll
        System.out.println("starting " + testInfo.getDisplayName() + " tags=" + testInfo.getTags());
    }

    @Test
    @Tag("fast")
    @DisplayName("reports its own metadata")
    void reports(TestInfo testInfo, TestReporter reporter) {
        reporter.publishEntry("displayName", testInfo.getDisplayName());
        reporter.publishEntry(Map.of("class", testInfo.getTestClass().map(Class::getSimpleName).orElse("?"),
                                     "tags", testInfo.getTags().toString()));
    }

    @RepeatedTest(3)
    void repeats(RepetitionInfo repetitionInfo, TestReporter reporter) {
        reporter.publishEntry("repetition",
                repetitionInfo.getCurrentRepetition() + "/" + repetitionInfo.getTotalRepetitions());
    }
}

go deeper

for a junior

Name the three built-in types, say what each gives you, and show a method signature that takes them.

for a middle

Explain that all injection goes through ParameterResolver, that constructors and lifecycle methods participate, and what the failure looks like when nothing supports a parameter.

for a senior

Discuss per-invocation resolution, TestReporter versus stdout under parallel execution, and how parameterized-test arguments occupy the leading parameters ahead of injected extras.

for a principal

Frame it as the extension seam that lets frameworks contribute to the test programming model without engine changes, and set team conventions for what may be injected.

## Dependency injection for tests In JUnit 4, a test method had to have an empty parameter list. JUnit 5 removed that restriction: **test constructors, test methods and lifecycle methods (`@BeforeAll`, `@BeforeEach`, `@AfterEach`, `@AfterAll`) may all declare parameters**, and the engine resolves each one at runtime through the `ParameterResolver` extension API. Nothing is injected by magic naming or type scanning — for every parameter, the engine asks the registered resolvers whether any of them supports it. ## The three built-in resolvers **`TestInfo`** describes the currently executing test or container. It is resolvable in test methods, lifecycle methods and test constructors. Its accessors are `getDisplayName()` (the resolved display name, including any `@DisplayName`), `getTags()` (the set of `@Tag` values in effect), `getTestClass()` and `getTestMethod()` (both `Optional`, because in `@BeforeAll` there is no method yet). Typical uses: log lines that identify the test, or deriving a unique record key so parallel tests do not collide on shared data. **`TestReporter`** has a single job: `publishEntry(...)`, in overloads taking a value, a key and value, or a `Map`. Entries are published to the launcher as *reporting entries* and surface in the IDE's test view and in report output attached to the specific test — a strictly better destination than `System.out`, which in a parallel run interleaves across tests and is often swallowed by the build tool. **`RepetitionInfo`** is resolvable only within a `@RepeatedTest` and the `@BeforeEach`/`@AfterEach` methods invoked for its repetitions. It exposes `getCurrentRepetition()` and `getTotalRepetitions()` (newer versions add failure-threshold accessors). Declaring it on a plain `@Test` is a configuration error: the resolver does not support it there and the test fails to start. ## What else arrives the same way The built-ins are only a floor. The same mechanism carries: - **Arguments of a parameterized test** — an internal resolver, registered by the parameterized-test invocation context, supplies the argument values for the leading parameters of each invocation. - **Framework objects** — Spring's `SpringExtension`, for example, resolves application-context beans into test parameters; Mockito's JUnit 5 extension resolves mocks annotated on parameters. - **Anything you write** — a custom `ParameterResolver` can inject a configured client, a fixture object, or a per-test resource. Because all of these use the same extension point, they compose: a method may take a parameterized argument, then a `TestInfo`, then a custom-resolved client — provided each parameter is supported by exactly one resolver. ## What happens when nothing supports a parameter The engine does not silently pass `null`. If no registered resolver returns `true` for a parameter, execution of that test fails before the body runs, with a `ParameterResolutionException` whose message names the parameter and the declaring executable — for example *No ParameterResolver registered for parameter [org.example.Client client] in method [...]*. The usual cause is a forgotten `@ExtendWith` for the extension that would have supplied it. ## Practical notes - **Parameter order does not matter for the built-ins.** They are matched by type, not position, so `void test(TestInfo info, TestReporter reporter)` and the reverse both work. For a parameterized test the argument parameters come *first*, with injected extras such as `TestInfo` after them. - **Constructor injection works too.** A test class constructor may take `TestInfo`; combined with `@TestInstance(Lifecycle.PER_CLASS)` this is a compact way to capture per-class metadata. - **`@BeforeAll` runs before any test instance exists**, so `TestInfo.getTestMethod()` is empty there — code defensively rather than calling `get()` blindly. - **Prefer `TestReporter` over printing.** It attaches output to the right test node and survives parallel execution. - Injection is **per invocation**: a fresh resolution happens for each test (and each repetition), so resolvers can hand out per-test isolated objects rather than shared ones. ## Why this design The alternative — a fixed set of framework-known types — would have meant every new capability required a change to the engine. Routing injection through a public extension interface means third-party libraries and your own code extend the test programming model without JUnit knowing anything about them, and the failure mode when something is missing is one clear exception naming the exact parameter.

  • Why prefer TestReporter.publishEntry over System.out.println in a test?
    A published entry is a reporting entry attached to the specific test node, so it appears in the IDE test view and in report output next to that test, and it survives parallel execution where interleaved stdout becomes unreadable. Build tools also frequently capture or discard stdout, whereas reporting entries flow through the launcher.
  • You declare `RepetitionInfo` on a plain `@Test` method. What happens?
    The built-in resolver only supports that type inside a `@RepeatedTest` and its per-repetition lifecycle methods, so nothing supports the parameter and the test fails before its body runs with a `ParameterResolutionException`. The fix is either to make it a `@RepeatedTest` or to drop the parameter.

saying these in an interview costs you the question

  • Thinking JUnit injects by parameter name or by scanning types without an extension
  • Expecting `null` to be passed when no resolver supports a parameter
  • Calling `TestInfo.getTestMethod().get()` inside `@BeforeAll`, where it is empty
  • Believing parameters are only allowed on test methods, not on constructors or lifecycle methods
  • Using `RepetitionInfo` outside a repeated test

context