How do I use @MethodSource for dynamic test data?

In JUnit 5, @MethodSource lets you supply test arguments from one or more factory methods. It is commonly used with @ParameterizedTest when your test data is too complex for @ValueSource or @CsvSource.

Basic example

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertTrue;

class StringTest {

    @ParameterizedTest
    @MethodSource("blankStrings")
    void shouldDetectBlankStrings(String input) {
        assertTrue(input == null || input.isBlank());
    }

    static Stream<String> blankStrings() {
        return Stream.of(null, "", " ", "\t", "\n");
    }
}

The method referenced by @MethodSource("blankStrings") provides the test data.

Supplying multiple arguments

If your test method has multiple parameters, return Stream<Arguments>.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @ParameterizedTest
    @MethodSource("additionCases")
    void shouldAddNumbers(int a, int b, int expected) {
        assertEquals(expected, a + b);
    }

    static Stream<Arguments> additionCases() {
        return Stream.of(
                Arguments.of(1, 2, 3),
                Arguments.of(5, 7, 12),
                Arguments.of(-1, 1, 0)
        );
    }
}

Using a method source without naming it

If the source method has the same name as the test method, you can omit the method name.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertTrue;

class NumberTest {

    @ParameterizedTest
    @MethodSource
    void isEven(int number) {
        assertTrue(number % 2 == 0);
    }

    static Stream<Integer> isEven() {
        return Stream.of(2, 4, 6, 8);
    }
}

Dynamic test data

You can generate test data dynamically inside the provider method.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.IntStream;

import static org.junit.jupiter.api.Assertions.assertTrue;

class RangeTest {

    @ParameterizedTest
    @MethodSource("numbersFromOneToTen")
    void shouldBePositive(int number) {
        assertTrue(number > 0);
    }

    static IntStream numbersFromOneToTen() {
        return IntStream.rangeClosed(1, 10);
    }
}

Using objects as test data

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertFalse;

class UserValidationTest {

    @ParameterizedTest
    @MethodSource("invalidUsers")
    void shouldRejectInvalidUsers(User user) {
        assertFalse(user.isValid());
    }

    static Stream<Arguments> invalidUsers() {
        return Stream.of(
                Arguments.of(new User("", "[email protected]")),
                Arguments.of(new User("Alice", "")),
                Arguments.of(new User(null, "[email protected]"))
        );
    }

    static class User {
        private final String name;
        private final String email;

        User(String name, String email) {
            this.name = name;
            this.email = email;
        }

        boolean isValid() {
            return name != null && !name.isBlank()
                    && email != null && !email.isBlank();
        }
    }
}

Referencing an external method source

You can also put test data providers in another class.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import static org.junit.jupiter.api.Assertions.assertEquals;

class MathTest {

    @ParameterizedTest
    @MethodSource("com.example.TestData#additionCases")
    void shouldAddNumbers(int a, int b, int expected) {
        assertEquals(expected, a + b);
    }
}
package com.example;

import org.junit.jupiter.params.provider.Arguments;

import java.util.stream.Stream;

public class TestData {

    static Stream<Arguments> additionCases() {
        return Stream.of(
                Arguments.of(1, 2, 3),
                Arguments.of(10, 20, 30)
        );
    }
}

Depending on your setup, make the provider method public static if it is in a different package or class.

Common rules

For standard JUnit 5 usage:

  • The test must use @ParameterizedTest.
  • The provider method usually must be static.
  • The provider method can return:
    • Stream<T>
    • Stream<Arguments>
    • Collection<T>
    • Iterable<T>
    • arrays
    • primitive streams like IntStream, LongStream, or DoubleStream
  • Use Arguments.of(...) when passing multiple values.
  • The number and types of provided arguments must match the test method parameters.

Example with readable test names

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

class DiscountTest {

    @ParameterizedTest(name = "{index}: price={0}, discount={1}, expected={2}")
    @MethodSource("discountCases")
    void shouldCalculateDiscount(double price, double discount, double expected) {
        assertEquals(expected, price * (1 - discount));
    }

    static Stream<Arguments> discountCases() {
        return Stream.of(
                Arguments.of(100.0, 0.10, 90.0),
                Arguments.of(200.0, 0.25, 150.0),
                Arguments.of(50.0, 0.00, 50.0)
        );
    }
}

In short, use @MethodSource when you want flexible, reusable, or dynamically generated test data for parameterized tests.

How do I write parameterized tests in JUnit?

In JUnit 5, parameterized tests let you run the same test multiple times with different input values.

You use:

@ParameterizedTest

instead of:

@Test

Then you provide test data using a source annotation such as @ValueSource, @CsvSource, @MethodSource, or @EnumSource.

Basic Example with @ValueSource

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static org.junit.jupiter.api.Assertions.assertTrue;

class NumberTest {

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 5, 10})
    void shouldBePositive(int number) {
        assertTrue(number > 0);
    }
}

This runs the test 4 times:

number = 1
number = 2
number = 5
number = 10

Strings with @ValueSource

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static org.junit.jupiter.api.Assertions.assertFalse;

class StringTest {

    @ParameterizedTest
    @ValueSource(strings = {"hello", "junit", "test"})
    void shouldNotBeBlank(String value) {
        assertFalse(value.isBlank());
    }
}

@ValueSource supports simple values such as:

@ValueSource(strings = {"a", "b"})
@ValueSource(ints = {1, 2, 3})
@ValueSource(longs = {1L, 2L})
@ValueSource(doubles = {1.5, 2.5})
@ValueSource(booleans = {true, false})

Multiple Arguments with @CsvSource

Use @CsvSource when each test case needs more than one value.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @ParameterizedTest
    @CsvSource({
            "1, 2, 3",
            "5, 7, 12",
            "10, -2, 8"
    })
    void shouldAddNumbers(int a, int b, int expected) {
        int result = a + b;

        assertEquals(expected, result);
    }
}

Each CSV row maps to the test method parameters:

a, b, expected

Use @NullSource, @EmptySource, and @NullAndEmptySource

These are useful for testing validation logic.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;

import static org.junit.jupiter.api.Assertions.assertTrue;

class UsernameValidatorTest {

    @ParameterizedTest
    @NullAndEmptySource
    @ValueSource(strings = {" ", "   "})
    void shouldRejectBlankUsernames(String username) {
        assertTrue(username == null || username.isBlank());
    }
}

This test runs with:

null
""
" "
"   "

You can also use them separately:

@NullSource
@EmptySource
@NullAndEmptySource

Use @MethodSource for Complex Test Data

Use @MethodSource when test data is more complex or easier to build in Java code.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

class DiscountCalculatorTest {

    @ParameterizedTest
    @MethodSource("discountCases")
    void shouldCalculateDiscount(int price, double discountRate, int expectedPrice) {
        int result = (int) (price * (1 - discountRate));

        assertEquals(expectedPrice, result);
    }

    static Stream<Arguments> discountCases() {
        return Stream.of(
                Arguments.of(100, 0.10, 90),
                Arguments.of(200, 0.25, 150),
                Arguments.of(80, 0.50, 40)
        );
    }
}

The method source is usually static, unless the test class uses a per-class test instance lifecycle.

Use @EnumSource for Enum Values

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import static org.junit.jupiter.api.Assertions.assertNotNull;

class RoleTest {

    enum Role {
        ADMIN,
        USER,
        GUEST
    }

    @ParameterizedTest
    @EnumSource(Role.class)
    void shouldHaveValidRole(Role role) {
        assertNotNull(role);
    }
}

You can also include or exclude specific enum values:

@EnumSource(value = Role.class, names = {"ADMIN", "USER"})

or:

@EnumSource(
        value = Role.class,
        names = {"GUEST"},
        mode = EnumSource.Mode.EXCLUDE
)

Naming Each Parameterized Test Run

You can customize the displayed name for each invocation:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @ParameterizedTest(name = "{0} + {1} should equal {2}")
    @CsvSource({
            "1, 2, 3",
            "5, 7, 12",
            "10, -2, 8"
    })
    void shouldAddNumbers(int a, int b, int expected) {
        assertEquals(expected, a + b);
    }
}

This can show test names like:

1 + 2 should equal 3
5 + 7 should equal 12
10 + -2 should equal 8

Common placeholders include:

{index}    the invocation index
{0}        the first argument
{1}        the second argument
{arguments} all arguments

Required Dependency

For Maven:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.13.4</version>
    <scope>test</scope>
</dependency>

For Gradle:

testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'

test {
    useJUnitPlatform()
}

Quick Rule of Thumb

Use:

  • @ValueSource for one simple argument
  • @CsvSource for several simple arguments
  • @MethodSource for complex objects or generated cases
  • @EnumSource for enum values
  • @NullSource, @EmptySource, or @NullAndEmptySource for null/empty validation cases

A typical parameterized test looks like this:

@ParameterizedTest
@CsvSource({
        "2, 3, 5",
        "10, 5, 15"
})
void shouldAddNumbers(int a, int b, int expected) {
    assertEquals(expected, a + b);
}