Spring MockRestServiceServer 处理对同一 URI 的多个请求(自动发现)
Spring MockRestServiceServer handling multiple requests to the same URI (auto-discovery)
假设我正在为 REST 服务 A 编写 Spring 集成测试。该服务依次访问另一个 REST 服务 B 并获取要访问 REST 服务 C 的 URI 列表。这是一种自动-发现模式。我想使用 MockRestServiceServer 模拟 B 和 C 响应。
现在来自 B 的响应是一个 URI 列表,它们都非常相似,为了示例起见,假设我来自 B 的响应是这样的:
{
uris: ["/stuff/1.json", "/stuff/2.json", "/stuff/39.json", "/stuff/47.json"]
}
只需服务 A 将它们中的每一个附加到服务 C 的基础 URL 上并发出这些请求。
模拟 B 很容易,因为它只有 1 个请求。
模拟 C 很麻烦,因为我必须模拟每个 URI 以进行适当的模拟响应。我想自动化它!
所以首先我编写自己的匹配器来匹配不是完整的 URL,而是匹配其中的一部分:
public class RequestContainsUriMatcher implements RequestMatcher {
private final String uri;
public RequestContainsUriMatcher(String uri){
this.uri = uri;
}
@Override
public void match(ClientHttpRequest clientHttpRequest) throws IOException, AssertionError {
assertTrue(clientHttpRequest.getURI().contains(uri));
}
}
这很好用,因为现在我可以这样做了:
public RequestMatcher requestContainsUri(String uri) {
return new RequestContainsUriMatcher(uri);
}
MockRestServiceServer.createServer(restTemplate)
.expect(requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
.andRespond(/* I will get to response creator */);
现在我需要的是一个知道完整请求 URL 和模拟数据所在位置的响应创建者(我将把它作为测试资源文件夹中的 json 文件):
public class AutoDiscoveryCannedDataResponseCreator implements ResponseCreator {
private final Function<String, String> cannedDataBuilder;
public AutoDiscoveryCannedDataResponseCreator(Function<String, String> cannedDataBuilder) {
this.cannedDataBuilder = cannedDataBuilder;
}
@Override
public ClientHttpResponse createResponse(ClientHttpRequest clientHttpRequest) throws IOException {
return withSuccess(cannedDataBuilder.apply(requestUri), MediaType.APPLICATION_JSON)
.createResponse(clientHttpRequest);
}
}
现在事情很简单,我必须编写一个构建器,将请求 URI 作为字符串,并将 returns 模拟数据作为字符串!太棒了!
public ResponseCreator withAutoDetectedCannedData() {
Function<String, String> cannedDataBuilder = new Function<String, String>() {
@Override
public String apply(String requestUri) {
//logic to get the canned data based on URI
return cannedData;
}
};
return new AutoDiscoveryCannedDataResponseCreator(cannedDataBuilder);
}
MockRestServiceServer.createServer(restTemplate)
.expect(requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
.andRespond(withAutoDetectedCannedData());
效果很好! ....对于第一个请求。
在第一个请求 (/stuff/1.json) 之后,我的 MockRestServiceServer 响应消息 "Assertion error: no further requests expected".
基本上,我可以向 MockRestServiceServer 发出与对它调用 .expect() 一样多的请求。因为我只有一个,所以只有第一个请求会通过。
有办法解决吗?我真的不想模拟服务 C 10 或 20 次...
编辑:请参阅@emeraldjava 的回答,其中显示了 Spring 4.3+ 用户的正确解决方案。
不幸的是,没有任何 nice 机制可以期待多次调用。您可以手动执行或使用循环,例如:
for (int i = 0; i < 10; i++) {
mockRestServiceServer
.expect(requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
.andRespond(withAutoDetectedCannedData());
}
请注意,必须在没有任何中断的情况下调用请求,例如不能有另一个与“/stuff”URI 不匹配的 REST 调用。
如果您查看 MockRestServiceServer class,它支持两种 'expect()' 方法。第一种默认为 'ExpectedCount.once()' 但第二种方法允许您更改此值
public ResponseActions expect(RequestMatcher matcher) {
return this.expect(ExpectedCount.once(), matcher);
}
public ResponseActions expect(ExpectedCount count, RequestMatcher matcher) {
return this.expectationManager.expectRequest(count, matcher);
}
我找到了这张票 MockRestServiceServer should allow for an expectation to occur multiple times,其中概述了第二种方法的一些选项。
在你的情况下,我认为添加静态导入和使用 manyTimes() 方法的代码比 for 循环更整洁
MockRestServiceServer
.expect(manyTimes(), requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
其他选项是
once();
manyTimes();
times(5);
min(2);
max(8);
between(3,6);
假设我正在为 REST 服务 A 编写 Spring 集成测试。该服务依次访问另一个 REST 服务 B 并获取要访问 REST 服务 C 的 URI 列表。这是一种自动-发现模式。我想使用 MockRestServiceServer 模拟 B 和 C 响应。
现在来自 B 的响应是一个 URI 列表,它们都非常相似,为了示例起见,假设我来自 B 的响应是这样的:
{
uris: ["/stuff/1.json", "/stuff/2.json", "/stuff/39.json", "/stuff/47.json"]
}
只需服务 A 将它们中的每一个附加到服务 C 的基础 URL 上并发出这些请求。
模拟 B 很容易,因为它只有 1 个请求。
模拟 C 很麻烦,因为我必须模拟每个 URI 以进行适当的模拟响应。我想自动化它!
所以首先我编写自己的匹配器来匹配不是完整的 URL,而是匹配其中的一部分:
public class RequestContainsUriMatcher implements RequestMatcher {
private final String uri;
public RequestContainsUriMatcher(String uri){
this.uri = uri;
}
@Override
public void match(ClientHttpRequest clientHttpRequest) throws IOException, AssertionError {
assertTrue(clientHttpRequest.getURI().contains(uri));
}
}
这很好用,因为现在我可以这样做了:
public RequestMatcher requestContainsUri(String uri) {
return new RequestContainsUriMatcher(uri);
}
MockRestServiceServer.createServer(restTemplate)
.expect(requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
.andRespond(/* I will get to response creator */);
现在我需要的是一个知道完整请求 URL 和模拟数据所在位置的响应创建者(我将把它作为测试资源文件夹中的 json 文件):
public class AutoDiscoveryCannedDataResponseCreator implements ResponseCreator {
private final Function<String, String> cannedDataBuilder;
public AutoDiscoveryCannedDataResponseCreator(Function<String, String> cannedDataBuilder) {
this.cannedDataBuilder = cannedDataBuilder;
}
@Override
public ClientHttpResponse createResponse(ClientHttpRequest clientHttpRequest) throws IOException {
return withSuccess(cannedDataBuilder.apply(requestUri), MediaType.APPLICATION_JSON)
.createResponse(clientHttpRequest);
}
}
现在事情很简单,我必须编写一个构建器,将请求 URI 作为字符串,并将 returns 模拟数据作为字符串!太棒了!
public ResponseCreator withAutoDetectedCannedData() {
Function<String, String> cannedDataBuilder = new Function<String, String>() {
@Override
public String apply(String requestUri) {
//logic to get the canned data based on URI
return cannedData;
}
};
return new AutoDiscoveryCannedDataResponseCreator(cannedDataBuilder);
}
MockRestServiceServer.createServer(restTemplate)
.expect(requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
.andRespond(withAutoDetectedCannedData());
效果很好! ....对于第一个请求。
在第一个请求 (/stuff/1.json) 之后,我的 MockRestServiceServer 响应消息 "Assertion error: no further requests expected".
基本上,我可以向 MockRestServiceServer 发出与对它调用 .expect() 一样多的请求。因为我只有一个,所以只有第一个请求会通过。
有办法解决吗?我真的不想模拟服务 C 10 或 20 次...
编辑:请参阅@emeraldjava 的回答,其中显示了 Spring 4.3+ 用户的正确解决方案。
不幸的是,没有任何 nice 机制可以期待多次调用。您可以手动执行或使用循环,例如:
for (int i = 0; i < 10; i++) {
mockRestServiceServer
.expect(requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
.andRespond(withAutoDetectedCannedData());
}
请注意,必须在没有任何中断的情况下调用请求,例如不能有另一个与“/stuff”URI 不匹配的 REST 调用。
如果您查看 MockRestServiceServer class,它支持两种 'expect()' 方法。第一种默认为 'ExpectedCount.once()' 但第二种方法允许您更改此值
public ResponseActions expect(RequestMatcher matcher) {
return this.expect(ExpectedCount.once(), matcher);
}
public ResponseActions expect(ExpectedCount count, RequestMatcher matcher) {
return this.expectationManager.expectRequest(count, matcher);
}
我找到了这张票 MockRestServiceServer should allow for an expectation to occur multiple times,其中概述了第二种方法的一些选项。
在你的情况下,我认为添加静态导入和使用 manyTimes() 方法的代码比 for 循环更整洁
MockRestServiceServer
.expect(manyTimes(), requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
其他选项是
once();
manyTimes();
times(5);
min(2);
max(8);
between(3,6);