Using SpringJUnit4ClassRunner with Parameterized – 使用带有参数化的SpringJUnit4ClassRunner

最后修改: 2019年 6月 4日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll see how to parameterize a Spring integration test implemented in JUnit4 with a Parameterized JUnit test runner.

在本教程中,我们将看到如何用参数化JUnit测试运行器对Spring集成测试实现参数化。

2. SpringJUnit4ClassRunner

2.SpringJUnit4ClassRunner

SpringJUnit4ClassRunner is an implementation of JUnit4’s ClassRunner that embeds Spring’s TestContextManager into a JUnit test.

SpringJUnit4ClassRunner是JUnit4的ClassRunner的一个实现,将Spring的TestContextManager嵌入到JUnit测试中

TestContextManager is the entry point into the Spring TestContext framework and therefore manages the access to Spring ApplicationContext and dependency injection in a JUnit test class. Thus, SpringJUnit4ClassRunner enables developers to implement integration tests for Spring components like controllers and repositories.

TestContextManager是进入Spring TestContext框架的入口,因此管理对Spring ApplicationContext的访问和JUnit测试类的依赖注入。因此,SpringJUnit4ClassRunner使开发者能够为Spring组件(如控制器和资源库)实施集成测试。

For example, we can implement an integration test for our RestController:

例如,我们可以为我们的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"));
    }
}

As can be seen from the test, our Controller accepts a user name as a path parameter and returns the user role accordingly.

从测试中可以看出,我们的Controller接受用户名作为路径参数,并相应地返回用户角色。

Now, in order to test this REST service with a different user name/role combination, we would have to implement a new test:

现在,为了用不同的用户名/角色组合测试这个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"));
}

This can quickly get out of hand for services where a large number of input combinations are possible.

对于可能有大量输入组合的服务来说,这可以很快失控。

To avoid this kind of repetition in our test classes, let’s see how to use Parameterized for implementing JUnit tests that accept multiple inputs.

为了避免在我们的测试类中出现这种重复,让我们看看如何使用Parameterized来实现接受多个输入的JUnit测试。

3. Using Parameterized

3.使用参数化

3.1. Defining Parameters

3.1.定义参数

Parameterized is a custom JUnit test runner that allows us to write a single test case and have it run against multiple input parameters:

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;
    }

    //...
}

As shown above, we used the @Parameters annotation to prepare the input parameters to be injected into the JUnit test. We also provided the mapping of these values in the @Parameter fields name and role.

如上所示,我们使用@Parameters注解来准备要注入到JUnit测试中的输入参数。我们还在@Parameter字段namerole中提供了这些值的映射。

But now, we have another problem to solve — JUnit doesn’t allow multiple runners in one JUnit test class. This means we can’t take advantage of SpringJUnit4ClassRunner to embed the TestContextManager into our test class. We’ll have to find another way to embed TestContextManager.

但是现在,我们有另一个问题需要解决–JUnit不允许在一个JUnit测试类中有多个运行器。这意味着我们不能利用SpringJUnit4ClassRunner来将TestContextManager嵌入我们的测试类中。我们必须找到其他方法来嵌入TestContextManager

Fortunately, Spring provides a couple of options for achieving this. We’ll discuss these in the following sections.

幸运的是,Spring为实现这一目标提供了几个选项。我们将在下面的章节中讨论这些。

3.2. Initializing the TestContextManager Manually

3.2.手动初始化TestContextManager

The first option is quite simple, as Spring allows us to initialize TestContextManager manually:

第一个选项非常简单,因为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();
    }

    //...
}

Notably, in this example, we used the Parameterized runner instead of the SpringJUnit4ClassRunner. Next, we initialized the TestContextManager in the setup() method.

值得注意的是,在这个例子中,我们使用了Parameterizedrunner而不是SpringJUnit4ClassRunner。接下来,我们在setup()方法中初始化了TestContextManager

Now, we can implement our parameterized JUnit test:

现在,我们可以实现我们的参数化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 will execute this test case twice — once for each set of inputs we defined using the @Parameters annotation.

JUnit将执行这个测试用例两次–对我们用@Parameters注解定义的每一组输入都执行一次。

3.3. SpringClassRule and SpringMethodRule

3.3.SpringClassRuleSpringMethodRule

Generally, it is not recommended to initialize the TestContextManager manually. Instead, Spring recommends using SpringClassRule and SpringMethodRule.

一般来说,不建议手动初始化TestContextManager。相反,Spring推荐使用SpringClassRuleSpringMethodRule

SpringClassRule implements JUnit’s TestRule — an alternate way to write test cases. TestRule can be used to replace the setup and cleanup operations that were previously done with @Before, @BeforeClass, @After, and @AfterClass methods.

SpringClassRule实现了JUnit的TestRule – 一种编写测试用例的替代方式。TestRule可以用来替代之前用@Before, @BeforeClass, @After,@AfterClass方法进行的设置和清理操作。

SpringClassRule embeds class-level functionality of TestContextManager in a JUnit test class. It initializes the TestContextManager and invokes the setup and cleanup of the Spring TestContext. Therefore, it provides dependency injection and access to the ApplicationContext.

SpringClassRuleTestContextManager的类级功能嵌入到JUnit测试类中。它初始化了TestContextManager,并调用了SpringTestContext的设置和清理。ApplicationContext的访问。

In addition to SpringClassRule, we must also use SpringMethodRule. which provides the instance-level and method-level functionality for TestContextManager.

除了SpringClassRule,我们还必须使用SpringMethodRule。它为TestContextManager提供实例级和方法级功能。

SpringMethodRule is responsible for the preparation of the test methods. It also checks for test cases that are marked for skipping and prevents them from running.

SpringMethodRule负责测试方法的准备。它还检查那些被标记为跳过的测试案例,并阻止它们运行。

Let’s see how to use this approach in our test class:

让我们看看如何在我们的测试类中使用这种方法。

@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. Conclusion

4.总结

In this article, we discussed two ways to implement Spring integration tests using the Parameterized test runner instead of SpringJUnit4ClassRunner. We saw how to initialize TestContextManager manually, and we saw an example using SpringClassRule with SpringMethodRule, the approach recommended by Spring.

在这篇文章中,我们讨论了使用Parameterized测试运行器而不是SpringJUnit4ClassRunner实现Spring集成测试的两种方法。我们看到了如何手动初始化TestContextManager,我们还看到了一个使用SpringClassRuleSpringMethodRule的例子,这是Spring推荐的方法。

Although we only discussed the Parameterized runner in this article, we can actually use either of these approaches with any JUnit runner to write Spring integration tests.

虽然我们在本文中只讨论了Parameterized runner,我们实际上可以使用这些方法中的任何一个JUnit runner来编写Spring集成测试。

As usual, all the example code is available over on GitHub.

像往常一样,所有的示例代码都可以在GitHub上找到。