RESTful 使用 RestTemplate 进行服务测试
RESTful Services test with RestTemplate
在我的应用程序中,我有很多 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 引导应用程序(带有嵌入式 tomcat)。
感谢帮助!
由于您正在使用 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())
);
}
}
此单元测试将在不部署的情况下测试 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'
文档中对此有很好的 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";
}
};
}
}
如果您不是在寻找端到端(集成)测试,MockRestServiceServer
可能会对您有所帮助。我发现将我的测试用例与真实服务分离非常有用。
Spring 医生说:
Used for tests that involve direct or indirect use of the 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.
这里是official doc
还有一个提示,requestTo
不能自动导入
server.expect(manyTimes(), requestTo("/hotels/42")) ....
是org.springframework.test.web.client.match.MockRestRequestMatchers
的静态方法
如果您使用 Spring 引导,您可以轻松地设置所有内容来测试您的 RestTemplate
如果您使用 @RestClientTest
注释您的测试。这确保自动配置您的应用程序所需的部分(RestTemplateBuilder
、ObjectMapper
、MockRestServiceServer
等)以测试您的客户端 类,例如:
@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。
在我的应用程序中,我有很多 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 引导应用程序(带有嵌入式 tomcat)。
感谢帮助!
由于您正在使用 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())
);
}
}
此单元测试将在不部署的情况下测试 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'
文档中对此有很好的 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";
}
};
}
}
如果您不是在寻找端到端(集成)测试,MockRestServiceServer
可能会对您有所帮助。我发现将我的测试用例与真实服务分离非常有用。
Spring 医生说:
Used for tests that involve direct or indirect use of the 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.
这里是official doc
还有一个提示,requestTo
不能自动导入
server.expect(manyTimes(), requestTo("/hotels/42")) ....
是org.springframework.test.web.client.match.MockRestRequestMatchers
如果您使用 Spring 引导,您可以轻松地设置所有内容来测试您的 RestTemplate
如果您使用 @RestClientTest
注释您的测试。这确保自动配置您的应用程序所需的部分(RestTemplateBuilder
、ObjectMapper
、MockRestServiceServer
等)以测试您的客户端 类,例如:
@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。