简体   繁体   English

使用 RestTemplate 进行 RESTful 服务测试

[英]RESTful Services test with RestTemplate

In my application I have a lot of REST- Services.在我的应用程序中,我有很多 REST 服务。 I have written tests for all services with:我已经为所有服务编写了测试:

org.springframework.web.client.RestTemplate

A REST- Service invokation eg looks like this: REST- 服务调用例如如下所示:

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

and I check the response body afterwards - all works fine.然后我检查响应正文 - 一切正常。 The disadvantage is, that the application for sure must be started in order to invoke the REST- Services.缺点是,必须启动应用程序才能调用 REST 服务。

My question now would be how I can do this in my JUnit- @Test methods?我现在的问题是如何在我的 JUnit-@Test 方法中做到这一点? It is a Spring Boot application (with embedded tomcat).它是一个 Spring Boot 应用程序(带有嵌入式 tomcat)。

Thanks for help!感谢帮助!

There's a good chapter on this in the documentation, I suggest you read through it to fully understand what you can do.文档中有一个很好的章节,我建议您通读一遍以完全了解您可以做什么。

I like to use @IntegrationTest with a custom configuration since that starts up the entire server and lets you test the complete system.我喜欢将@IntegrationTest与自定义配置一起使用,因为它会启动整个服务器并让您测试整个系统。 If you want to replace certain parts of the system with mocks you can do that by excluding certain configurations or beans and replacing them with your own.如果你想用模拟替换系统的某些部分,你可以通过排除某些配置或 bean 并用你自己的替换它们来实现。

Here's a small example.这是一个小例子。 I've left out the MessageService interface because it's obvious from IndexController what it does, and it's default implementation - DefaultMessageService - because it's not relevant.我省略了MessageService接口,因为从IndexController很明显它做了什么,并且它是默认实现 - DefaultMessageService - 因为它不相关。

What it does is that it spins up the entire application minus the DefaultMessageService but with it's own MessageService instead.它的作用是启动整个应用程序,减去DefaultMessageService而是使用它自己的MessageService It then uses RestTemplate to issue real HTTP requests to the running application in the test case.然后它使用RestTemplate向测试用例中正在运行的应用程序发出真实的 HTTP 请求。

Application classes:应用类:

IntegrationTestDemo.java: IntegrationTestDemo.java:

@SpringBootApplication
public class IntegrationTestDemo {

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

}

IndexController.java:索引控制器.java:

@RestController
public class IndexController {

    @Autowired
    MessageService messageService;

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

Test classes:测试类:

IntegrationTestDemoTest.java: 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:测试配置.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";
            }
        };
    }
}

If you were not looking for a end to end (integretion) test, the MockRestServiceServer might help you.如果您不是在寻找端到端(集成)测试, MockRestServiceServer可能会帮助您。 I found it's very useful to de-couple my test cases from a real service.我发现将我的测试用例与真实服务分离非常有用。

Spring doc said:春天医生说:

Used for tests that involve direct or indirect use of the RestTemplate.用于涉及直接或间接使用 RestTemplate 的测试。 Provides a way to set up expected requests that will be performed through the RestTemplate as well as mock responses to send back thus removing the need for an actual server .提供一种方法来设置将通过 RestTemplate 执行的预期请求以及发送回的模拟响应,从而消除对实际服务器的需要

Here is the official doc这是官方文档


One more tip is that, requestTo can not be imported automatically还有一个提示是, requestTo不能自动导入

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

It's a static method of org.springframework.test.web.client.match.MockRestRequestMatchers这是org.springframework.test.web.client.match.MockRestRequestMatchers的静态方法

Since you are using Spring MVC for REST, I would recommend using the testing facilities supplied by instantiating MockMVC() - enabling tests such as:由于您使用 Spring MVC for 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())
        );
    }
}

This unit test will test a REST interface without deploying.此单元测试将在不部署的情况下测试 REST 接口。 Specifically, whether exactly 8 users are returned and the first one has the fields 'id', 'alias' and 'name'.具体来说,是否准确地返回了 8 个用户,第一个用户是否具有字段“id”、“alias”和“name”。

The jsonPath assertions require two dependencies: jsonPath 断言需要两个依赖项:

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

And probably also:可能还有:

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

If you use Spring Boot, you can easily setup everything to test your RestTemplate if you annotate your test with @RestClientTest .如果您使用 Spring Boot,如果您使用RestTemplate注释您的测试,您可以轻松设置所有内容来测试您的@RestClientTest This ensures to auto-configure the required parts ( RestTemplateBuilder , ObjectMapper , MockRestServiceServer , etc.) of your application to test your client classes like eg:这确保自动配置您的应用程序所需的部分( 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();

  }
}

The corresponding test (using JUnit 5) looks like the following:相应的测试(使用 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());
  }

}

This setup allows you to specify stub HTTP responses using MockRestServiceServer .此设置允许您使用MockRestServiceServer指定存根 HTTP 响应。

I've provided a more detailed tutorial for this, if wan to learn more about it.如果想要了解更多信息,我已经为此提供了更详细的教程

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM