How do you point a JUnit 5 @MethodSource at a factory method that lives in a different class, and why would you keep shared test data there rather than duplicating it in each test class?
answer
- "fully.qualified.Class#methodName" in the annotation value
- External factory must be static and reachable
- #method(java.lang.String) disambiguates overloads
- Shared set = no drift; add an edge case once, all tests gain
- Cost: indirection + class-name string is not compiler-checked
basics
~10 sGive the fully qualified class name, then #, then the method name: @MethodSource("com.acme.TestData#validEmails"). The external method must be static. It lets several test classes share one authoritative set of cases instead of copying rows.
solid answer
~50 sUse the `FQCN#methodName` form: ```java @ParameterizedTest @MethodSource("com.acme.fixtures.EmailFixtures#validAddresses") void accepts(String address) { ... } ``` The referenced method must be **static** — the PER_CLASS escape hatch only applies to factories in the test class itself — and must be accessible to the test. If overloads exist you can disambiguate by listing parameter types in parentheses, e.g. `#values(java.lang.String)`. **Why bother:** the same set of cases often matters to several tests — a validator test, a mapper test, a controller test all care about "valid email addresses". Centralising them means adding a newly discovered edge case in one place makes every consumer stronger, and no test drifts to a stale copy. It also gives the data a name that documents intent (`EmailFixtures.validAddresses`), and keeps it in ordinary compiled Java, so refactoring and "find usages" still work — unlike a class-name string, which no tool tracks. Balance that against the cost: a reader of the test must now open another file to see the data.
code
java · 5 lines@ParameterizedTest
@MethodSource("com.acme.fixtures.EmailFixtures#validAddresses")
void acceptsValidAddresses(String address) {
assertTrue(EmailValidator.isValid(address));
}go deeper
Recall the FQCN#method syntax and that the external method must be static.
Add the accessibility and overload-disambiguation rules, and give a concrete reason to share — several tests needing the same case set.
Argue the drift problem: a shared factory means a newly found edge case strengthens every consumer at once; balance that against indirection and coupling.
Decide fixture ownership and placement — small domain-named fixture classes, a composed annotation to contain the fragile string, and an explicit decision before publishing fixtures across modules.
## The syntax A `@MethodSource` value is normally a bare method name resolved inside the test class. It can instead be a **fully qualified reference**: ``` <fully.qualified.ClassName>#<methodName> ``` ```java @ParameterizedTest @MethodSource("com.acme.fixtures.EmailFixtures#validAddresses") void acceptsValidAddresses(String address) { assertTrue(EmailValidator.isValid(address)); } ``` Rules that apply: - **The external method must be `static`.** `@TestInstance(PER_CLASS)` relaxes the static requirement only for factories declared in the test class itself; there is no instance of a foreign fixture class for JUnit to use. - **It must be reachable** from the test class — same module/classpath, and not `private` to another class (a `private` factory works only inside the declaring test class, where reflection has the necessary access; for a foreign class use at least package-private in the same package, or `public`). - **Overloads can be disambiguated** by adding a parameter-type list: `"com.acme.Fixtures#values(java.lang.String)"`. In practice, avoid overloading fixture factories at all and the question never arises. - **Several references can be listed:** `@MethodSource({"com.acme.Fixtures#happy", "com.acme.Fixtures#edge"})` concatenates the element sequences. The return-type and element-shape rules are unchanged: a `Stream`/`Iterable`/array of `Arguments` for multi-parameter tests, of bare values for single-parameter ones. ## Why centralise the data ### One authoritative case set Some sets of inputs are facts about the domain, not about one test: the valid and invalid email formats, the currency codes you support, the IBAN samples, the malformed-payload corpus, every enum-plus-expected-label pairing. Several tests care about them — a validator unit test, a mapper test, a serialization round-trip test, a controller slice test. If each class keeps its own copy, they drift. Someone finds a new edge case (an address with a plus tag, an IBAN with lowercase letters), fixes the validator, and adds the case to *one* test. The other three keep passing against their stale copies, and the next regression slips through the ones that were never updated. A shared factory makes the improvement automatic: add the case once, and every consuming test immediately covers it. ### It is code, not a string Comparing with the file-based alternative (`@CsvFileSource`): a shared fixture class is compiled Java. Rename refactorings, "find usages", type checking and IDE navigation all work on the *method*. The one weak link is the class-name string inside the annotation — many IDEs resolve and refactor it, but it is not a compiler guarantee, so a moved fixture class can leave a dangling reference that only fails at run time with a "could not load class" style error. Keeping fixtures in a stable, rarely moved package limits that exposure. ### Naming as documentation `EmailFixtures.validAddresses()` says what the data is. The same rows inlined in a `@CsvSource` say nothing beyond their contents. Good fixture names turn the test signature into a readable sentence: *this test accepts all valid addresses*. ## The costs, stated honestly **Indirection.** A reader of the test can no longer see the data. For three obvious cases this is a net loss — keep them inline. Externalise when the set is either large or genuinely shared. **Coupling.** Once five tests consume one factory, adding a case can break tests you were not thinking about. That is usually a feature (it found a real gap), but it does mean the factory is now shared code and deserves the same care as production code: stable name, focused scope, a comment explaining what the set is meant to represent. **Temptation to over-generalise.** A `TestData` class that accumulates every fixture in the project becomes a junk drawer. Prefer several small, domain-named fixture classes (`EmailFixtures`, `IbanFixtures`) over one god class, and keep each factory's set narrow enough that its name is honest. ## Where to put fixtures A common layout is a `fixtures` (or `testdata`) package under `src/test/java`, mirroring the production package it supports. If two Gradle/Maven modules need the same fixtures, the data has to move into a shared test-fixtures artefact — at that point you are publishing test data as a real dependency, and it deserves an explicit decision rather than a copy-paste. ## Composing with other sources A shared factory pairs naturally with the multi-value form: a test can take the shared happy-path set plus a locally declared edge case, listing both names. And when the same *annotation* configuration recurs, wrap it in a custom composed annotation (`@ValidEmails`) that carries the `@MethodSource` reference — consumers then never repeat the fully qualified string, which also removes the fragile-string problem from every call site but one. ## Interview summary Syntax `FQCN#method`, method must be static and reachable, overloads disambiguated with a parameter-type list, several references may be listed. Do it when the case set is shared or large; keep it inline when it is small and local; treat the fixture class as shared code with a real name and a narrow scope.
- Does @TestInstance(PER_CLASS) let you use a non-static factory in another class?No. PER_CLASS only affects the lifecycle of the *test* class, allowing non-static factories declared in that same class. A factory in a different class must be static, because JUnit has no instance of that foreign class and no rule for creating one.
- What is the risk of the fully qualified class name being a string in the annotation?It is not verified by the compiler, so moving or renaming the fixture class can leave a dangling reference that only fails at run time when the source cannot be resolved. Many IDEs do refactor it, but not all tooling does. Keeping fixtures in a stable package and hiding the reference behind one composed annotation reduces the number of fragile strings to one.
saying these in an interview costs you the question
- Using a simple class name instead of the fully qualified name
- Expecting an instance method in a foreign class to work under PER_CLASS
- Piling every fixture in the project into a single TestData god class
- Externalizing a three-row table and forcing readers into a second file
- Assuming the annotation string is checked at compile time