【问题标题】:Testing a call to a rest controller from controller in Spring在 Spring 中测试从控制器对休息控制器的调用
【发布时间】:2015-10-08 06:47:12
【问题描述】:

我正在使用 RestTemplate 从控制器调用休息控制器,例如:

@RequestMapping(method = POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @ResponseBody
    public String formForUpload(@RequestParam("file") MultipartFile file) throws Exception {
    final MultiValueMap<String, Object> parts=...;
    restTemplate.postForObject("http://localhost:8080/rest/something",
                        parts, MultipartFile.class);
...
}

如何单元测试控制器调用的 URL 是否正确。有什么方法可以获取其余控制器的路径(例如使用这种反射技术)?

我不想运行集成测试!

【问题讨论】:

  • 在方法中,你要放置一个显式的字符串文字,所以你不能在测试中使用相同的字符串文字吗?
  • 我不确定我是否关注你!如果您认为您有解决方案,请发布。我想测试对该控制器的调用,该控制器从前端的表单接收帖子,并确保我正在使用我想模拟的 RestTemplate 调用正确的休息控制器。假设我更改了rest服务的api,那么测试肯定会失败,因为路径不是最新的。在断言中检查这一点会很好。
  • 您要解决的问题是什么?如何测试使用三个参数(即 URL 字符串、部分和类)调用模拟?还是第一个参数,URL 字符串被正确构造以匹配 REST 控制器映射到的 URL?

标签: spring rest unit-testing testing


【解决方案1】:

也许这不是正确的答案,但也许有帮助。

假设其余控制器(比如说MyRestController)的类声明用@RequestMapping注释,那么我假设您可以简单地获取当前定义的路径,如下所示:

final String controllerPath = MyRestController.class
                    .getAnnotation(RequestMapping.class).value()[0];

更新 您为什么不尝试对代码进行以下更改。它们基于 Spring、JUnit 和 Mockito。假设您的控制器是 MyController 类。

@Test
    public void testUploadPostCallsToRestService() throws Exception {
        final MockMultipartFile file = //create a mock for a file to be uploaded;

        final MyController myController = new MyController();

        final RestTemplate restTemplate = new RestTemplate();
        final MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);

        ReflectionTestUtils.setField(myController, "restTemplate", restTemplate); //I assume you use Spring MVC from your tagging, isn't it?

        final MockMvc mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
mockServer.expect(requestTo("http://localhost:8080/rest/something"))
                .andExpect(method(POST))
                .andRespond(withStatus(OK)); //Or whatever your rest returns

        mockMvc.perform(fileUpload("http://localhost:8080/path/to/my/controller")
                .file(file)
                .accept(MediaType.MULTIPART_FORM_DATA))
                .andExpect(status().isOk()); //I hope you return this :)

        mockServer.verify();
    }

这对我来说非常有效。本质上,我将模拟用于必须为RestTemplate 提供服务的其余服务服务器。因此,后者将调用行为如您指定的模拟。然后,使用反射将其注入您的控制器。最后,我使用 MockMvc 上传文件并从控制器获取答案。

【讨论】:

    【解决方案2】:

    方法参数值是否捕获了您要查找的内容?

    您可以使用模拟框架来模拟其余模板并捕获方法调用参数。

    我建议使用TestNGJMockit

    控制器类(示例是工作代码,所以我想展示整个控制器类):

    import org.springframework.beans.factory.annotation.*;
    import org.springframework.http.MediaType;
    import org.springframework.util.*;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.client.RestTemplate;
    import org.springframework.web.multipart.MultipartFile;
    
    public class MyController {
    
        @Autowired
        @Qualifier("MyRestTemplate")
        private RestTemplate restTemplate;
    
        @RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
        @ResponseBody
        public String formForUpload(@RequestParam("file") MultipartFile file) {
            MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
            restTemplate.postForObject("http://localhost:8080/rest/something", parts, MultipartFile.class);
            return "[{\"good-data\"}]";
        }
    }
    

    单元测试:

    import mockit.*;
    import org.springframework.mock.web.MockMultipartFile;
    import org.springframework.web.client.RestTemplate;
    import org.springframework.web.multipart.MultipartFile;
    import org.testng.Assert;
    import org.testng.annotations.Test;
    
    import java.util.ArrayList;
    
    public class MyControllerTest {
    
        @Test
        public void testFormForUpload(@Mocked final RestTemplate restTemplate) {
            MyController controller = new MyController();
            Deencapsulation.setField(controller, restTemplate);
    
            final ArrayList<String> urlValueHolder = new ArrayList<>();
    
            new Expectations() {{
                restTemplate.postForObject(withCapture(urlValueHolder), any, MultipartFile.class);
            }};
    
            controller.formForUpload(new MockMultipartFile("data-file.txt", "various data".getBytes()));
    
            Assert.assertEquals(urlValueHolder.size(), 1);
            Assert.assertEquals(urlValueHolder.get(0), "http://localhost:8080/rest/something");
        }
    }
    

    注意事项:

    • RestTemplate 模拟作为测试方法的参数传递。这就是创建工作模拟所需要做的一切。
    • 请参阅我用作RestTemplate#postForObject 的第一个参数的withCapture 调用。控制器使用的 url(s) 将存储在列表 urlValueHolder 中。
    • url 列表有两个断言 - 它被调用一次并且它包含所需的 url。

    我相信测试很简单,但我仍然建议重构控制器。我建议不要使用模拟和测试控制器来验证 url,而是将 url 创建移动到单独的工厂类。只需对工厂进行简单的单元测试即可测试 url。

    【讨论】:

    • 我会尽快测试这个!抱歉迟到了!
    • 如果您定义了相应的 bean(在 XML 或 Java 配置中定义的具体 RestTemplate),它确实有效。您可以使用问题中的原始控制器 sn-p 。无论如何,它对单元测试没有任何影响,因为它在 Spring 上下文之外实例化了控制器。
    【解决方案3】:

    Spring Hateoas 项目中有一个类用于生成控制器和控制器方法的链接。具体看看org.springframework.hateoas.mvc.ControllerLinkBuilder。它将能够为简单的@RequestMapping 注释生成 URL,但是如果您在同一个处理程序方法上使用多个映射,它将无法确定要生成哪一个。

    另外,如果你的控制器处理方法返回String,链接构建器会抛出一个错误,所以将返回类型更改为Object,它就会工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-31
      • 2019-02-01
      • 1970-01-01
      • 2018-07-31
      • 1970-01-01
      • 2014-12-01
      • 2019-03-27
      • 2017-03-10
      相关资源
      最近更新 更多