«1. Обзор

В этом руководстве мы увидим, как параметризовать интеграционный тест Spring, реализованный в JUnit4, с помощью средства запуска тестов Parameterized JUnit.

2. SpringJUnit4ClassRunner

SpringJUnit4ClassRunner — это реализация класса JUnit4 ClassRunner, которая встраивает TestContextManager Spring в тест JUnit.

TestContextManager является точкой входа в структуру Spring TestContext и поэтому управляет доступом к Spring ApplicationContext и внедрением зависимостей в тестовом классе JUnit. Таким образом, SpringJUnit4ClassRunner позволяет разработчикам реализовывать интеграционные тесты для компонентов Spring, таких как контроллеры и репозитории.

Например, мы можем реализовать интеграционный тест для нашего RestController:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebConfig.class)
public class RoleControllerIntegrationTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    private static final String CONTENT_TYPE = "application/text;charset=ISO-8859-1";

    @Before
    public void setup() throws Exception {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void givenEmployeeNameJohnWhenInvokeRoleThenReturnAdmin() throws Exception {
        this.mockMvc.perform(MockMvcRequestBuilders
          .get("/role/John"))
          .andDo(print())
          .andExpect(MockMvcResultMatchers.status().isOk())
          .andExpect(MockMvcResultMatchers.content().contentType(CONTENT_TYPE))
          .andExpect(MockMvcResultMatchers.content().string("ADMIN"));
    }
}

Как видно из теста, наш контроллер принимает имя пользователя в качестве параметра пути и соответственно возвращает роль пользователя.

Теперь, чтобы протестировать эту службу REST с другой комбинацией имени пользователя/роли, нам нужно будет реализовать новый тест:

@Test
public void givenEmployeeNameDoeWhenInvokeRoleThenReturnEmployee() throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders
      .get("/role/Doe"))
      .andDo(print())
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.content().contentType(CONTENT_TYPE))
      .andExpect(MockMvcResultMatchers.content().string("EMPLOYEE"));
}

Это может быстро выйти из-под контроля для служб, где большое количество возможны комбинации входов.

Чтобы избежать такого повторения в наших тестовых классах, давайте посмотрим, как использовать Parameterized для реализации тестов JUnit, которые принимают несколько входных данных.

3. Использование параметризованных

3.1. Определение параметров

Parameterized — это специальный инструмент запуска тестов JUnit, который позволяет нам написать один тестовый пример и запустить его с несколькими входными параметрами:

@RunWith(Parameterized.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebConfig.class)
public class RoleControllerParameterizedIntegrationTest {

    @Parameter(value = 0)
    public String name;

    @Parameter(value = 1)
    public String role;

    @Parameters
    public static Collection<Object[]> data() {
        Collection<Object[]> params = new ArrayList();
        params.add(new Object[]{"John", "ADMIN"});
        params.add(new Object[]{"Doe", "EMPLOYEE"});

        return params;
    }

    //...
}

Как показано выше, мы использовали аннотацию @Parameters для подготовки входных данных. параметры, которые будут введены в тест JUnit. Мы также предусмотрели сопоставление этих значений в имени поля @Parameter и роли.

Но теперь нам нужно решить другую проблему — JUnit не допускает нескольких исполнителей в одном тестовом классе JUnit. Это означает, что мы не можем использовать преимущества SpringJUnit4ClassRunner для встраивания TestContextManager в наш тестовый класс. Нам придется найти другой способ встраивания TestContextManager.

К счастью, Spring предоставляет несколько возможностей для достижения этой цели. Мы обсудим их в следующих разделах.

3.2. Инициализация TestContextManager вручную

Первый вариант довольно прост, поскольку Spring позволяет нам инициализировать TestContextManager вручную:

@RunWith(Parameterized.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebConfig.class)
public class RoleControllerParameterizedIntegrationTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    private TestContextManager testContextManager;

    @Before
    public void setup() throws Exception {
        this.testContextManager = new TestContextManager(getClass());
        this.testContextManager.prepareTestInstance(this);

        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    //...
}

Примечательно, что в этом примере мы использовали Parameterized runner вместо SpringJUnit4ClassRunner. Затем мы инициализировали TestContextManager в методе setup().

Теперь мы можем реализовать наш параметризованный тест JUnit:

@Test
public void givenEmployeeNameWhenInvokeRoleThenReturnRole() throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders
      .get("/role/" + name))
      .andDo(print())
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.content().contentType(CONTENT_TYPE))
      .andExpect(MockMvcResultMatchers.content().string(role));
}

JUnit выполнит этот тестовый пример дважды — один раз для каждого набора входных данных, который мы определили с помощью аннотации @Parameters.

3.3. SpringClassRule и SpringMethodRule

Обычно не рекомендуется инициализировать TestContextManager вручную. Вместо этого Spring рекомендует использовать SpringClassRule и SpringMethodRule.

SpringClassRule реализует TestRule JUnit — альтернативный способ написания тестовых случаев. TestRule можно использовать для замены операций настройки и очистки, которые ранее выполнялись с помощью методов @Before, @BeforeClass, @After и @AfterClass.

SpringClassRule встраивает функциональность TestContextManager на уровне класса в тестовый класс JUnit. Он инициализирует TestContextManager и вызывает настройку и очистку Spring TestContext. Следовательно, он обеспечивает внедрение зависимостей и доступ к ApplicationContext.

В дополнение к SpringClassRule мы также должны использовать SpringMethodRule. который обеспечивает функциональность уровня экземпляра и уровня метода для TestContextManager.

SpringMethodRule отвечает за подготовку тестовых методов. Он также проверяет наличие тестовых наборов, помеченных для пропуска, и предотвращает их выполнение.

Давайте посмотрим, как использовать этот подход в нашем тестовом классе:

@RunWith(Parameterized.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebConfig.class)
public class RoleControllerParameterizedClassRuleIntegrationTest {
    @ClassRule
    public static final SpringClassRule scr = new SpringClassRule();

    @Rule
    public final SpringMethodRule smr = new SpringMethodRule();

    @Before
    public void setup() throws Exception {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    //...
}

4. Заключение

«В этой статье мы обсудили два способа реализации интеграционных тестов Spring с использованием средства запуска тестов с параметрами вместо SpringJUnit4ClassRunner. Мы увидели, как вручную инициализировать TestContextManager, и мы увидели пример использования SpringClassRule с SpringMethodRule — подход, рекомендованный Spring.

Хотя в этой статье мы обсуждали только параметризованный бегун, на самом деле мы можем использовать любой из этих подходов с любым исполнителем JUnit для написания интеграционных тестов Spring.

Как обычно, весь код примера доступен на GitHub.