Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Данные из метода: @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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.