How does JUnit 4's `Parameterized` runner work — where does the data come from, how does each data row reach the test instance, and how many instances of the test class are created?
answer
- @RunWith(Parameterized.class)
- public static @Parameters → Iterable<Object[]> / Object[][]
- constructor injection OR public @Parameter(i) fields
- cross-product: methods × rows, fresh instance each
- name = "{index}: {0} + {1} = {2}"
basics
~20 sAnnotate the class @RunWith(Parameterized.class) and add a public static method annotated @Parameters returning Iterable<Object[]> or Object[][]. Each row is injected through a matching constructor, or into public fields annotated @Parameter(index). One instance is created per row per test method.
solid answer
~50 s```java @RunWith(Parameterized.class) public class AdditionTest { @Parameters(name = "{index}: {0} + {1} = {2}") public static Iterable<Object[]> data() { … } } ``` The `@Parameters` method must be **public static**, is called once before the class runs, and returns the data rows. Each row reaches the test in one of two ways: a constructor whose parameters match the row, or **public** fields annotated `@Parameter(0)`, `@Parameter(1)`, … with a default constructor. The runner creates the **cross-product of test methods and data rows** — every `@Test` method runs once per row, on a fresh instance built from that row. The `name` attribute controls the display name via placeholders `{index}`, `{0}`, `{1}`, defaulting to the index alone. Extras worth knowing: `@Parameterized.BeforeParam` / `@AfterParam` (public static methods run before/after all tests for one parameter set, JUnit 4.13), and `@Parameterized.UseParametersRunnerFactory` to supply a custom per-parameter child runner.
code
java · 20 lines@RunWith(Parameterized.class)
public class SlugifierTest {
@Parameters(name = "{index}: slugify({0}) = {1}")
public static Object[][] data() {
return new Object[][] {
{ "Hello World", "hello-world" },
{ " padded ", "padded" },
{ "a//b", "a-b" }
};
}
@Parameter(0) public String input;
@Parameter(1) public String expected;
@Test
public void slugifies() {
assertEquals(expected, Slugifier.slugify(input));
}
}go deeper
Recall the annotations — @RunWith(Parameterized.class), public static @Parameters, constructor or @Parameter fields — and that every test runs once per row.
Explain the cross-product model, both injection styles, the name template, and when the @Parameters method runs.
Add @BeforeParam/@AfterParam, UseParametersRunnerFactory, the runtime cost of the cross-product, and diagnostics for the common initialization errors.
Judge when a data table beats separate named tests, and how parameterized classes affect suite runtime, report readability and failure triage at scale.
## The shape of a parameterized test ```java @RunWith(Parameterized.class) public class AdditionTest { @Parameters(name = "{index}: {0} + {1} = {2}") public static Iterable<Object[]> data() { return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 }, { 3, 2, 5 } }); } private final int a, b, sum; public AdditionTest(int a, int b, int sum) { this.a = a; this.b = b; this.sum = sum; } @Test public void adds() { assertEquals(sum, a + b); } } ``` ## Where the data comes from One method annotated `@Parameters`, which must be **public and static**. It may return `Iterable<Object[]>`, an `Object[][]`, or — when there is exactly one parameter — an `Iterable` or array of plain objects. It is invoked once, before any test runs; an exception thrown there is an initialization error and the whole class fails. Because it is static it cannot use per-instance state, and because it runs early it should not depend on setup performed by `@Before`. ## How a row reaches the instance Two injection styles: 1. **Constructor injection.** The runner picks the constructor and passes the row's elements positionally. Types must be assignment-compatible; a mismatch is an initialization error naming the parameter count or type. 2. **Field injection.** Declare a default constructor and mark **public** fields with `@Parameter(0)`, `@Parameter(1)`, …. The index defaults to 0, so a single-parameter test can simply write `@Parameter`. Field injection avoids long constructors but loses `final`. ## How many instances One test-class instance per (data row × test method) pair — the runner documents its model as the *cross-product* of test methods and data elements. Internally it builds one child runner per parameter set, and that child runner behaves like the default runner, creating a fresh instance per method. So a class with 3 methods and 5 rows executes 15 tests, and no state leaks between rows. ## Naming `@Parameters(name = "…")` accepts placeholders resolved by `MessageFormat`: `{index}` for the row number and `{0}`, `{1}`, … for parameter values. Without it the display name is just the index, which makes a failing report nearly unreadable — "test[3]" tells you nothing. Always set a name. Watch out for values whose `toString()` is enormous or contains characters your report format dislikes; braces in a value also need care because `MessageFormat` interprets them. ## Per-parameter fixtures JUnit 4.13 added `@Parameterized.BeforeParam` and `@Parameterized.AfterParam`: public static void methods that run once before and once after all tests for a given parameter set. They may take no arguments or the full parameter list. This fills the gap between `@BeforeClass` (once for the whole class) and `@Before` (every test). ## Customising the child runner `@Parameterized.UseParametersRunnerFactory(MyFactory.class)` replaces the factory that produces the per-parameter runner (the default produces a `BlockJUnit4ClassRunnerWithParameters`). This is the supported way for another framework to participate in a parameterized class despite the one-runner-per-class rule — the framework ships a factory rather than a runner. ## Common problems - **"No public static parameters method on class X"** — the method is missing, non-public, non-static, or the annotation is misspelled. - **Constructor mismatch** — the row's arity or types do not match; count the columns. - **Field injection silently empty** — the `@Parameter` fields must be public; a private field cannot be set. - **Mutable shared data** — rows returned from a static structure that tests mutate will bleed across rows; build fresh values in `data()`. - **Unreadable reports** — no `name` attribute. ## When to use it Parameterization is right when the *same* assertions apply to many inputs — boundary tables, format parsers, calculation matrices. It is wrong when each row needs a slightly different assertion, which is when people start branching inside the test on the parameter value: that is two tests wearing one coat.
- If a class has 4 `@Test` methods and the `@Parameters` method returns 5 rows, how many tests execute?Twenty. The runner builds the cross-product of test methods and data rows, creating a child runner per parameter set and a fresh test-class instance per method within it. That is also why parameterized classes multiply runtime quickly — a heavy `@Before` is paid once per row per method.
- What are the two ways a parameter row reaches the test instance, and when would you choose each?A constructor whose parameters match the row positionally, or public fields annotated `@Parameter(index)` with a default constructor. Constructors keep fields `final` and make the arity explicit, which suits two or three parameters. Field injection avoids a long, unreadable constructor when there are many columns, at the cost of non-final fields and the requirement that the fields be public.
saying these in an interview costs you the question
- Declaring the `@Parameters` method non-static or non-public — it must be `public static`.
- Using private fields with `@Parameter`; the runner cannot set them.
- Believing one instance is reused for all rows; each row gets fresh instances.
- Omitting the `name` attribute and then complaining that failure reports say only `test[3]`.
- Branching inside the test body on the parameter value, which means the rows are not really the same test.