What rules must a JUnit 5 @MethodSource factory method satisfy — must it be static, which return types are accepted — and how do you supply several arguments per invocation versus a single one?
answer
- static unless @TestInstance(PER_CLASS)
- Stream / IntStream / Iterable / Iterator / Collection / array
- JUnit closes the returned Stream (Files.lines is safe)
- arguments(a, b) per element for multi-param; bare value for one
- Called once per test method, may be private, takes no args
basics
~20 sThe factory must be static unless the test class is annotated @TestInstance(PER_CLASS). It may return Stream (including IntStream/LongStream/DoubleStream), Iterable, Iterator, Collection or an array. Each element is an Arguments/Object[] for multi-parameter tests, or a bare value for a single-parameter test.
solid answer
~50 s**Static by default.** JUnit creates a fresh test instance per test method (`PER_METHOD` lifecycle), so at argument-resolution time there is no instance to call an instance method on. Annotating the class `@TestInstance(TestInstance.Lifecycle.PER_CLASS)` gives one instance for the whole class and then non-static factories are allowed. **Return types.** `Stream<?>`, the primitive streams (`IntStream`, `LongStream`, `DoubleStream`), `Iterable`, `Iterator`, `Collection`, and object or primitive arrays. JUnit closes a returned `Stream` after consuming it, so `Files.lines(...)` is safe. **Element shape.** - Multi-parameter test → each element is `Arguments.of(a, b)` (shorthand `arguments(a, b)`) or an `Object[]`. - Single-parameter test → a bare value is enough: `Stream.of("a", "bb")` or `IntStream.of(1, 2, 3)`. The factory is called **once**, not once per invocation, and takes no arguments in normal use. Its visibility can be `private` — JUnit accesses it reflectively — though package-private or `static` in a shared class is more usual.
code
java · 13 lines@ParameterizedTest
@MethodSource("discountCases")
void appliesDiscount(Order order, Tier tier, BigDecimal expected) {
assertEquals(expected, pricing.discountFor(order, tier));
}
static Stream<Arguments> discountCases() {
return Stream.of(
arguments(new Order(100), Tier.GOLD, new BigDecimal("10.00")),
arguments(new Order(100), Tier.BASIC, BigDecimal.ZERO),
arguments(new Order(0), Tier.GOLD, BigDecimal.ZERO)
);
}go deeper
Know that the factory is static and returns a Stream of Arguments, and that a single-parameter test can skip the Arguments wrapper.
List the accepted return types, the PER_CLASS exception, and that the factory is called once and its stream is closed by JUnit.
Discuss the trade of PER_CLASS (shared instance state), null arguments, and the type-safety gap of untyped Arguments plus the record-per-case alternative.
Treat factories as shared fixture code with real design pressure — placement, reuse and typing decisions that affect every consumer, not incidental test plumbing.
## Why static is the default JUnit Jupiter's default test-instance lifecycle is `PER_METHOD`: a new instance of the test class is constructed for **every** test invocation, so that state cannot leak between tests. Argument resolution, however, happens *before* those instances exist — the engine must know how many invocations there are in order to create them. With no instance available, the factory has to be `static`. The escape hatch is the lifecycle itself: ```java @TestInstance(TestInstance.Lifecycle.PER_CLASS) class OrderTest { Stream<Arguments> cases() { ... } // non-static is now legal } ``` `PER_CLASS` creates one instance for the whole class, so a non-static factory (and non-static `@BeforeAll`) becomes possible. The trade is real: a single instance means fields are shared across all tests in the class, and you become responsible for resetting them. Reach for `PER_CLASS` when the factory genuinely needs instance state — an injected fixture, a value computed in a constructor — not to save typing `static`. A factory declared in a **different** class must always be static, regardless of the test class's lifecycle. ## Accepted return types JUnit accepts anything it can iterate: - `Stream<?>` — the common choice, including `Stream<Arguments>`. - `IntStream`, `LongStream`, `DoubleStream` — handy for numeric single-parameter tests: `IntStream.rangeClosed(1, 100)`. - `Iterable<?>`, `Iterator<?>`, `Collection<?>` — a `List<Arguments>` is perfectly idiomatic. - Arrays — `Object[][]` for multi-parameter cases, `int[]`/`String[]` for single-parameter ones. JUnit **closes** a returned `Stream` after consuming it. That makes ```java static Stream<String> lines() throws IOException { return Files.lines(Path.of("src/test/resources/cases.txt")); } ``` safe — no leaked file handle. The corollary is that you must not hand back a stream you plan to reuse elsewhere, and never a stream that has already been consumed (that throws `IllegalStateException` and the failure looks unrelated to the test). The factory itself is invoked **once per test method**, not once per invocation, so any expensive setup inside it is paid once. ## Element shape: the rule people get wrong The element type must match the *arity* of the test method: **Several parameters → `Arguments` per element.** ```java static Stream<Arguments> discounts() { return Stream.of( arguments(new Order(100), Tier.GOLD, new BigDecimal("10.00")), arguments(new Order(100), Tier.BASIC, BigDecimal.ZERO) ); } ``` `Arguments.of(...)` and its static shorthand `arguments(...)` both live in `org.junit.jupiter.params.provider.Arguments`. An `Object[]` per element works identically but loses readability. **One parameter → bare values.** ```java static Stream<String> blanks() { return Stream.of("", " ", "\t"); } ``` Wrapping single values in `Arguments` is legal; forgetting to wrap when there are two parameters is not, and produces an argument-count failure. ## Arguments and null `arguments(null, 3)` is fine — factories are the cleanest way to feed `null` into a parameterized test, since the value is a real reference rather than a text convention. Note the compiler may warn about an ambiguous varargs call with a lone `null`; cast it (`arguments((String) null)`) to be explicit. ## Typing and generics `Arguments` is untyped, so the compiler cannot check that the values in a row match the test method's parameter types. A wrong type surfaces at runtime as an argument-conversion or `ClassCastException`-style failure on the affected invocation only. If a table is large and homogeneous, an alternative is a factory returning `Stream<MyCase>` — a small record with named components — bound to a *single* parameter of that type; the test then reads `case.input()` and `case.expected()`, and the compiler checks everything. That pattern trades JUnit's per-argument display names for type safety, and is worth knowing as an option. ## Signature details - **Visibility** does not matter: `private static Stream<Arguments> cases()` works, since the engine uses reflection. Package-private is the common house style. - **Parameters**: keep factories argument-free. A no-arg factory is the portable, always-correct form. - **Checked exceptions** may be declared — `throws IOException` is fine, and a thrown exception fails the test method's container rather than one invocation. - **Return null or an empty stream** and you get no invocations, which JUnit reports as a failure by default rather than a silent pass — a good default, since a source that produced nothing is almost always a bug. ## Interview summary Static unless `PER_CLASS`; returns any iterable-ish type and JUnit closes streams; `Arguments` per element for multi-parameter tests and bare values for single-parameter ones; called once, not per invocation.
- Why must the factory be static by default, and what exactly changes under @TestInstance(PER_CLASS)?With the default PER_METHOD lifecycle a new test instance is created for each invocation, and argument resolution happens before those instances exist, so there is nothing to invoke a non-static method on. PER_CLASS creates a single instance for the whole class, so non-static factories (and non-static @BeforeAll) become legal. The cost is shared mutable state across the class's tests, which you must reset yourself.
- Is it safe to return Files.lines(path) from a @MethodSource factory?Yes. JUnit closes the returned Stream after it has consumed all elements, so the file handle is released. The same guarantee means you must not return a stream you intend to consume elsewhere, and returning an already-consumed stream fails with IllegalStateException.
- What does JUnit do if the factory returns an empty stream?By default it reports a failure for that parameterized test rather than passing silently, because a source that yields zero invocations is nearly always a mistake — a filter that removed everything, or a fixture file that failed to load. Treat it as a signal to check the factory, not as something to suppress.
saying these in an interview costs you the question
- Claiming instance factories always work, without mentioning PER_CLASS
- Believing the factory runs once per invocation
- Thinking the factory must be public
- Returning Stream<String> for a two-parameter test method
- Closing the returned stream manually inside the factory