Data from a method: @MethodSource
@ValueSource and @CsvSource hold only strings and numbers written right in the annotation. Sometimes the data is more complex: objects, lists, values that must be computed. Then the data is returned by an ordinary static method — a factory method — and @MethodSource connects it to the test.
A factory method
Let us check the discount with several cases at once. Create src/test/java/com/example/shop/DiscountTest.java:
// src/test/java/com/example/shop/DiscountTest.java
package com.example.shop;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.math.BigDecimal;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
class DiscountTest {
private final PriceCalculator calculator = new PriceCalculator();
static Stream<Arguments> discounts() {
return Stream.of(
Arguments.of(new BigDecimal("50.00"), 0, new BigDecimal("50.00")),
Arguments.of(new BigDecimal("50.00"), 10, new BigDecimal("45.00")),
Arguments.of(new BigDecimal("19.99"), 15, new BigDecimal("16.99")),
Arguments.of(new BigDecimal("50.00"), 100, new BigDecimal("0.00"))
);
}
@ParameterizedTest(name = "{0} minus {1}% = {2}")
@MethodSource("discounts")
void discount(BigDecimal amount, int percent, BigDecimal expected) {
assertEquals(expected, calculator.applyDiscount(amount, percent));
}
}discounts() returns a stream of Arguments — each Arguments.of(...) is one run, the values go to the test parameters in order. In the IDE the runs are called 50.00 minus 10% = 45.00 and so on. Maven reports Tests run: 4.
Rules for the method
| Rule | Detail |
|---|---|
static | The method is called before the test instance exists |
| No parameters | JUnit calls it itself |
| Returns a stream, a collection or an array | Stream<Arguments>, List<String>, int[] |
| The name | Given in @MethodSource("discounts"); without a name JUnit looks for a method with the same name as the test |
If the test has a single parameter, Arguments is not needed — return the values directly:
// src/test/java/com/example/blog/SlugsTest.java
static Stream<String> titles() {
return Stream.of("Hello", "Hello, World!", "JUnit & Mockito");
}
@ParameterizedTest
@MethodSource("titles")
void slugIsLowerCase(String title) {
String slug = Slugs.of(title);
assertEquals(slug.toLowerCase(), slug);
}Data from another class
When several test classes need the same data, the factory method moves to a separate class, and the reference includes the full class name: @MethodSource("com.example.shop.TestData#discounts"). Such an external method must also be static.
Which source to choose
| Source | When |
|---|---|
@ValueSource | One parameter of a simple type |
@CsvSource | Several simple parameters, a table "input → expected" |
@CsvFileSource | The same table but in a .csv file in src/test/resources |
@MethodSource | Objects, computed values, shared data |
@EnumSource | All or some values of an enum |
Common mistake
The factory method was written as an ordinary instance method, without static:
Stream<String> titles() { ... }Each run of the test then fails with a configuration error:
org.junit.platform.commons.PreconditionViolationException: Method 'java.util.stream.Stream<java.lang.String> com.example.blog.NonStaticSourceTest.titles()' must be static: local factory methods must be static unless the PER_CLASS @TestInstance lifecycle mode is used; external factory methods must always be static.Add static. The message also mentions the alternative — @TestInstance(Lifecycle.PER_CLASS) on the class, then JUnit creates one instance for all tests; beginners are better off with static.