Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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

RuleDetail
staticThe method is called before the test instance exists
No parametersJUnit calls it itself
Returns a stream, a collection or an arrayStream<Arguments>, List<String>, int[]
The nameGiven 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

SourceWhen
@ValueSourceOne parameter of a simple type
@CsvSourceSeveral simple parameters, a table "input → expected"
@CsvFileSourceThe same table but in a .csv file in src/test/resources
@MethodSourceObjects, computed values, shared data
@EnumSourceAll 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.

Thanks for reading!

Donate to the site — we work hard for you.