将 mock 注入 Spring MockMvc WebApplicationContext

Posted

技术标签:

【中文标题】将 mock 注入 Spring MockMvc WebApplicationContext【英文标题】:Inject mock into Spring MockMvc WebApplicationContext 【发布时间】:2015-10-27 10:53:40 【问题描述】:

我正在使用 Spring-boot 测试(通过 JUnit4 和 Spring MockMvc)一个 REST 服务适配器。适配器只是将向它发出的请求传递给另一个 REST 服务(使用自定义 RestTemplate)并将附加数据附加到响应中。

我想运行 MockMvc 测试来执行控制器集成测试,但想用模拟覆盖控制器中的 RestTemplate 以允许我预定义第 3 方 REST 响应并防止它在每次测试。我已经能够通过实例化MockMvcBuilders.standAloneSetup() 并将其传递给控制器​​以使用post 中列出的注入的模拟进行测试(以及下面的设置)来完成此操作,但是我无法使用MockMvcBuilders.webAppContextSetup().

我浏览过其他几篇文章,但没有一篇回答关于如何实现这一点的问题。我想在测试中使用实际的 Spring 应用程序上下文而不是独立的,以防止在应用程序可能增长时出现任何空白。

编辑:我使用 Mockito 作为我的模拟框架,并试图将其中一个模拟注入到上下文中。如果这不是必需的,那就更好了。

控制器:

@RestController
@RequestMapping(Constants.REQUEST_MAPPING_PATH)
public class Controller

    @Autowired
    private DataProvider dp;    

    @Autowired
    private RestTemplate template;

    @RequestMapping(value = Constants.REQUEST_MAPPING_RESOURCE, method = RequestMethod.GET)
    public Response getResponse(
            @RequestParam(required = true) String data,
            @RequestParam(required = false, defaultValue = "80") String minScore
            ) throws Exception 

        Response resp = new Response();

        // Set the request params from the client request
        Map<String, String> parameters = new HashMap<String, String>();
        parameters.put(Constants.PARAM_DATA, data);
        parameters.put(Constants.PARAM_FORMAT, Constants.PARAMS_FORMAT.JSON);

        resp = template.getForObject(Constants.RESTDATAPROVIDER_URL, Response.class, parameters);

        if(resp.getError() == null)
            resp.filterScoreLessThan(new BigDecimal(minScore));
            new DataHandler(dp).populateData(resp.getData());
        
        return resp;
    

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = MainSpringBootAdapter.class)
@TestPropertySource("/application-junit.properties")
public class WacControllerTest 

    private static String controllerURL = Constants.REQUEST_MAPPING_PATH + Constants.REQUEST_MAPPING_RESOURCE + compressedParams_all;
    private static String compressedParams_all = "?data=data&minScore=minScore";

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @InjectMocks
    private Controller Controller;

    @Mock
    private RestTemplate rt;

    @Value("$file")
    private String file;

    @Spy
    private DataProvider dp;

    @Before
    public void setup() throws Exception 
        dp = new DataProvider(file);    
        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    

    @Test
    public void testGetResponse() throws Exception 

        String[] strings = "requestData", "100";

        Mockito.when(
            rt.getForObject(Mockito.<String> any(), Mockito.<Class<Object>> any(), Mockito.<Map<String, ?>> any()))
            .thenReturn(populateTestResponse());

        mockMvc.perform(get(controllerURL, strings)
            .accept(Constants.APPLICATION_JSON_UTF8))
            .andDo(MockMvcResultHandlers.print());

        Mockito.verify(rt, Mockito.times(1)).getForObject(Mockito.<String> any(), Mockito.<Class<?>> any(), Mockito.<Map<String, ?>> any());

        


        private Response populateTestResponse() 
            Response  resp = new Response();

            resp.setScore(new BigDecimal(100));
            resp.setData("Some Data");

            return resp;
    

【问题讨论】:

【参考方案1】:

Spring 的MockRestServiceServer 正是您要找的。

类的javadoc的简短描述:

客户端 REST 测试的主要入口点。用于涉及直接或间接(通过客户端代码)使用 RestTemplate 的测试。提供了一种对将通过 RestTemplate 执行的请求设置细粒度期望的方法,以及一种定义要发回的响应的方法,从而无需实际运行的服务器。

尝试像这样设置您的测试:

@WebAppConfiguration
@ContextConfiguration(classes = YourSpringConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class ExampleResourceTest 

    private MockMvc mockMvc;
    private MockRestServiceServer mockRestServiceServer;

    @Autowired
    private WebApplicationContext wac;

    @Autowired
    private RestOperations restOperations;

    @Before
    public void setUp() throws Exception 
        mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        mockRestServiceServer = MockRestServiceServer.createServer((RestTemplate) restOperations);
    


    @Test
    public void testMyApiCall() throws Exception 
        // Following line verifies that our code behind /api/my/endpoint made a REST PUT
        // with expected parameters to remote service successfully
        expectRestCallSuccess();

        this.mockMvc.perform(MockMvcRequestBuilders.get("/api/my/endpoint"))
            .andExpect(status().isOk());
    

    private void expectRestCallSuccess() 
        mockRestServiceServer.expect(
            requestTo("http://remote.rest.service/api/resource"))
            .andExpect(method(PUT))
            .andRespond(withSuccess("\"message\": \"hello\"", APPLICATION_JSON));
    



【讨论】:

在看到其他帖子后,我实际上已经尝试过使用它,但没有任何运气。在经历了很多挫折之后,我终于今天能够让它工作,并且没有意识到包含覆盖我的RestTemplate bean 的其他 @Configuration 也覆盖了 MockRestServiceServer 注入的 bean。我将提出另一种方法来说明如何在没有MockRestServiceServer 的情况下完成同样的事情。非常感谢。【参考方案2】:

这是另一个解决方案。简单地说,它只是创建一个新的RestTemplate bean 并覆盖已经注册的那个。

因此,虽然它执行的功能与 @mzc 答案相同,但它允许我使用 Mockito 更轻松地制作响应和验证匹配器。

这不仅仅是几行代码,但它也避免了必须添加额外的代码来将 Response 对象转换为上述 mockRestServiceServer.expect().andRespond(&lt;String&gt;) 方法的 arg 的字符串。

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = MainSpringBootAdapter.class)
@TestPropertySource("/application-junit.properties")
public class WacControllerTest 

    private static String Controller_URL = Constants.REQUEST_MAPPING_PATH + Constants.REQUEST_MAPPING_RESOURCE + compressedParams_all;

    @Configuration
        static class Config 
            @Bean
            @Primary
            public RestTemplate restTemplateMock() 
                return Mockito.mock(RestTemplate.class);
        
    

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @InjectMocks
    private Controller Controller;

    @Mock
    private RestTemplate rt;

    @Value("$file")
    private String file;

    @Spy
    private DataProvider dp;

    @Before
    public void setup() throws Exception 
        dp = new DataProvider(file); 

        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        this.rt = (RestTemplate) this.wac.getBean("restTemplateMock");
    

    @Test
    public void testGetResponse() throws Exception 

        String[] strings = "request", "100";

        //Set the request params from the client request
        Map<String, String> parameters = new HashMap<String, String>();
        parameters.put(Constants.PARAM_SINGLELINE, strings[0]);
        parameters.put(Constants.PARAM_FORMAT, Constants.PARAMS_FORMAT.JSON);

        Mockito.when(
            rt.getForObject(Mockito.<String> any(), Mockito.<Class<Object>> any(), Mockito.<Map<String, ?>> any()))
            .thenReturn(populateTestResponse());

        mockMvc.perform(get(Controller_URL, strings)
            .accept(Constants.APPLICATION_JSON_UTF8))
            .andDo(MockMvcResultHandlers.print());

        Mockito.verify(rt, Mockito.times(1)).getForObject(Mockito.<String> any(), Mockito.<Class<?>> any(), Mockito.<Map<String, ?>> any());

        


        private Response populateTestResponse() 
            Response  resp = new Response();

            resp.setScore(new BigDecimal(100));
            resp.setData("Some Data");

            return resp;
    

【讨论】:

我向不应该被命名的对手发誓(!),春天充满了谜团。这就像一个魅力。我几乎要开始实施配置文件了,还有什么不是的,但它做到了!谢谢。我的问题:为什么会这样?我尝试在另一个 \@Configuration 类中创建一个新的 \@Bean,但 Spring 抱怨有重复的 bean 和所有内容。是什么让它起作用? @apil.tamang @Configuration 是 bean 的 java 配置。它与 xml 配置同义。在这种情况下,我们正在连接另一个 RestTemplate bean,但返回它的模拟实例。此外,@Primary“表示当多个候选者有资格自动装配单值依赖项时,应优先考虑一个 bean” 好的。在我尝试过之前只是一个快速的。如果没有@Primary 注解,这个方案可以工作吗?除了您已经说过的以外,@Primary 有什么特别之处? @apil.tamang 如果我没记错的话,将使用 Spring 的产品 RestTemplate。不是你需要的模拟。您可能需要稍后查看@BeanMock,以确保在测试之外扫描应用程序组件时不会发现此新 bean @ethesx 谢谢,你让我开心。我认为生活中有比spring创建和组合测试上下文的方式更简单的事情:)【参考方案3】:

org.springframework.boot.test.mock.mockito.MockBean@MockBean 帮了我。

【讨论】:

以上是关于将 mock 注入 Spring MockMvc WebApplicationContext的主要内容,如果未能解决你的问题,请参考以下文章

spring boot 测试无法注入 TestRestTemplate 和 MockMvc

Spring Boot MVC 测试 - MockMvc 始终为空

Spring Mock

@CurrentSecurityContext 总是在 mockMvc 集成测试中注入 null?

Spring4 Spring MVC实战——MockMvc报org.springframework.core.CollectionFactory.createLinkedMap错误

MockMVC 将测试控制器与会话范围 bean 集成