【问题标题】:RESTful Services test with RestTemplate使用 RestTemplate 进行 RESTful 服务测试
【发布时间】:2015-10-22 18:21:37
【问题描述】:

在我的应用程序中,我有很多 REST 服务。我已经为所有服务编写了测试:

org.springframework.web.client.RestTemplate

一个 REST- 服务调用,例如看起来像这样:

final String loginResponse = restTemplate.exchange("http://localhost:8080/api/v1/xy", HttpMethod.POST, httpEntity, String.class)
        .getBody();

然后我检查响应正文 - 一切正常。 缺点是,必须启动应用程序才能调用 REST 服务。

我现在的问题是如何在我的 JUnit-@Test 方法中做到这一点? 它是一个 Spring Boot 应用程序(带有嵌入式 tomcat)。

感谢您的帮助!

【问题讨论】:

标签: java rest junit spring-boot


【解决方案1】:

在文档中有一个很好的chapter,我建议您通读它以充分了解您可以做什么。

我喜欢将@IntegrationTest 与自定义配置一起使用,因为它会启动整个服务器并让您测试整个系统。如果您想用模拟替换系统的某些部分,您可以通过排除某些配置或 bean 并用您自己的替换它们来实现。

这是一个小例子。我省略了MessageService 接口,因为从IndexController 可以明显看出它的作用,并且它是默认实现 - DefaultMessageService - 因为它不相关。

它的作用是启动整个应用程序减去DefaultMessageService,而是使用它自己的MessageService。然后它使用RestTemplate 向测试用例中正在运行的应用程序发出真正的HTTP 请求。

应用程序类:

IntegrationTestDemo.java:

@SpringBootApplication
public class IntegrationTestDemo {

    public static void main(String[] args) {
        SpringApplication.run(IntegrationTestDemo.class, args);
    }

}

IndexController.java:

@RestController
public class IndexController {

    @Autowired
    MessageService messageService;

    @RequestMapping("/")
    String getMessage() {
        return messageService.getMessage();
    }
}

测试类:

IntegrationTestDemoTest.java:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfig.class)
@WebIntegrationTest // This will start the server on a random port
public class IntegrationTestDemoTest {

    // This will hold the port number the server was started on
    @Value("${local.server.port}")
    int port;

    final RestTemplate template = new RestTemplate();

    @Test
    public void testGetMessage() {
        String message = template.getForObject("http://localhost:" + port + "/", String.class);

        Assert.assertEquals("This is a test message", message);
    }
}

TestConfig.java:

@SpringBootApplication
@ComponentScan(
    excludeFilters = {
        // Exclude the default message service
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = DefaultMessageService.class),
        // Exclude the default boot application or it's
        // @ComponentScan will pull in the default message service
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = IntegrationTestDemo.class)
    }
)
public class TestConfig {

    @Bean
    // Define our own test message service
    MessageService mockMessageService() {
        return new MessageService() {
            @Override
            public String getMessage() {
                return "This is a test message";
            }
        };
    }
}

【讨论】:

  • 它工作得很好 - 感谢您的提示和您的示例!
【解决方案2】:

如果您不是在寻找端到端(集成)测试,MockRestServiceServer 可能会对您有所帮助。我发现将测试用例与真实服务分离非常有用。

Spring 文档说:

用于涉及直接或间接使用 RestTemplate 的测试。提供一种方法来设置将通过 RestTemplate 执行的预期请求以及发送回的模拟响应,从而无需实际服务器

这里是official doc


还有一个提示是,requestTo 不能自动导入

server.expect(manyTimes(), requestTo("/hotels/42")) ....

org.springframework.test.web.client.match.MockRestRequestMatchers的静态方法

【讨论】:

  • 这非常适合测试您编写的 Dto 是否映射到您将从其余服务中获得的预期 Json。这几乎是测试 RestTemplate 使用情况的用例。
【解决方案3】:

由于您使用 Spring MVC 进行 REST,我建议使用实例化 MockMVC() 提供的测试工具 - 启用测试,例如:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
 ... // any required Spring config
)
@WebAppConfiguration
public class RestControllerTest {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }


    @Test
    public void getUserList() throws Exception {
        mockMvc.perform(get("/user"))
            .andExpect(status().isOk())
            .andExpect(content().contentType("application/json;charset=UTF-8")) 
            .andExpect(content().encoding("UTF-8"))
            .andExpect(jsonPath("$", hasSize(8)))
            .andExpect(jsonPath("$[0].id").exists())
            .andExpect(jsonPath("$[0].alias").exists())
            .andExpect(jsonPath("$[0].name").exists())
        );
    }
}

此单元测试将在不部署的情况下测试 REST 接口。具体来说,是否正好返回了 8 个用户,并且第一个用户有 'id'、'alias' 和 'name' 字段。

jsonPath 断言需要两个依赖项:

'com.jayway.jsonpath:json-path:0.8.1'
'com.jayway.jsonpath:json-path-assert:0.8.1'

也许还有:

'org.springframework:spring-test:4.1.7.RELEASE'

【讨论】:

  • 这并没有真正回答问题,因为它没有显示如何设置MockMVC。您应该发布完整的答案或直接向 OP 提出问题并发表评论。
  • 我已经更新了答案并同意评论更合适。但是,由于我的评论需要发布一些代码,因此我选择将其设为一个不完整的答案,我将删除或扩展它。
  • 是否可以使用 MockMvc 将 JSON 反序列化为 Java 对象?
  • 请问这样做的目的是什么?
  • @Jordão 是的。只需在链调用结束时调用 andReturn() 方法即可获取 MvcResult 对象。比你可以使用 mvcResult.getResponse.getContentAsString() 并使用你的解组器反序列化 JSON。
【解决方案4】:

如果您使用 Spring Boot,如果您使用 @RestClientTest 注释您的测试,您可以轻松设置一切来测试您的 RestTemplate。这可确保自动配置应用程序所需的部分(RestTemplateBuilderObjectMapperMockRestServiceServer 等)以测试您的客户端类,例如:

@Component
public class UserClient {

  private final RestTemplate restTemplate;

  public UserClient(RestTemplateBuilder restTemplateBuilder) {
    this.restTemplate = restTemplateBuilder.rootUri("https://reqres.in").build();
  }

  public User getSingleUser(Long id) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);

    HttpEntity<Void> requestEntity = new HttpEntity<>(headers);

    return this.restTemplate
      .exchange("/api/users/{id}", HttpMethod.GET, requestEntity, User.class, id)
      .getBody();

  }
}

相应的测试(使用 JUnit 5)如下所示:

@RestClientTest(UserClient.class)
class UserClientTest {

  @Autowired
  private UserClient userClient;

  @Autowired
  private ObjectMapper objectMapper;

  @Autowired
  private MockRestServiceServer mockRestServiceServer;

  @Test
  public void userClientSuccessfullyReturnsUserDuke() throws Exception {

    String json = this.objectMapper
      .writeValueAsString(new User(new UserData(42L, "duke@java.org", "duke", "duke", "duke")));

    this.mockRestServiceServer
      .expect(requestTo("/api/users/42"))
      .andRespond(withSuccess(json, MediaType.APPLICATION_JSON));

    User result = userClient.getSingleUser(42L);

    assertEquals(42L, result.getData().getId());
    assertEquals("duke", result.getData().getFirstName());
    assertEquals("duke", result.getData().getLastName());
    assertEquals("duke", result.getData().getAvatar());
    assertEquals("duke@java.org", result.getData().getEmail());
  }

}

此设置允许您使用 MockRestServiceServer 指定存根 HTTP 响应。

我为此提供了更多detailed tutorial,如果想了解更多信息。

【讨论】:

    猜你喜欢
    • 2017-06-04
    • 2016-11-06
    • 2013-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多