Данные из метода: @MethodSource
@ValueSource и @CsvSource хранят только строки и числа, записанные прямо в аннотации. Иногда данные сложнее: объекты, списки, значения, которые нужно вычислить. Тогда их возвращает обычный статический метод — фабричный метод, — а @MethodSource связывает его с тестом.
Фабричный метод
Проверим скидку сразу на нескольких случаях. Создайте 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} минус {1}% = {2}")
@MethodSource("discounts")
void discount(BigDecimal amount, int percent, BigDecimal expected) {
assertEquals(expected, calculator.applyDiscount(amount, percent));
}
}discounts() возвращает поток Arguments: каждый Arguments.of(...) — один запуск, значения по порядку попадают в параметры теста. В IDE запуски называются 50.00 минус 10% = 45.00 и так далее. Maven сообщает Tests run: 4.
Правила для метода
| Правило | Подробности |
|---|---|
static | Метод вызывается до того, как появится экземпляр теста |
| Без параметров | JUnit вызывает его сам |
| Возвращает поток, коллекцию или массив | Stream<Arguments>, List<String>, int[] |
| Имя | Указывается в @MethodSource("discounts"); без имени JUnit ищет метод с тем же именем, что у теста |
Если у теста один параметр, Arguments не нужен — возвращайте значения напрямую:
// 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);
}Данные из другого класса
Когда одни и те же данные нужны нескольким тестовым классам, фабричный метод переносят в отдельный класс, а ссылка включает полное имя класса: @MethodSource("com.example.shop.TestData#discounts"). Такой внешний метод тоже должен быть static.
Какой источник выбрать
| Источник | Когда |
|---|---|
@ValueSource | Один параметр простого типа |
@CsvSource | Несколько простых параметров, таблица «вход → ожидание» |
@CsvFileSource | Та же таблица, но в файле .csv в src/test/resources |
@MethodSource | Объекты, вычисляемые значения, общие данные |
@EnumSource | Все или некоторые значения перечисления |
Частая ошибка
Фабричный метод написали обычным методом экземпляра, без static:
Stream<String> titles() { ... }Тогда каждый запуск теста падает с ошибкой конфигурации:
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.Добавьте static. Сообщение упоминает и альтернативу — @TestInstance(Lifecycle.PER_CLASS) на классе, тогда JUnit создаёт один экземпляр на все тесты; новичкам проще со static.