Spring MockRestServiceServer 处理对同一 URI 的多个请求(自动发现)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30713734/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring MockRestServiceServer handling multiple requests to the same URI (auto-discovery)
提问by Daniel Gruszczyk
Let's say I am writing Spring integration tests for a REST service A. This service in turn hits another REST service B and gets a list of URIs to hit on REST service C. It is kind of auto-discovery pattern. I want to mock B and C responses using MockRestServiceServer.
Now the response from B is a list of URIs, they are all very similar, and for the sake of the example lets say my response from B is like so:
假设我正在为 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"]
}
Simply service A will append each of them onto base URL for service C and make those requests.
Mocking B is easy since it is only 1 request.
Mocking C is a hassle as I would have to mock every single URI to appropriate mock response. I want to automate it!
So first I write my own matcher to match not a full URL, but part of it:
只需服务 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));
}
}
This works fine as now I can do this:
这很好用,因为现在我可以这样做:
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 */);
Now all I need is a response creator that knows the full request URL and where the mock data sits (I will have it as json files in test resources folder):
现在我需要的是一个响应创建者,它知道完整的请求 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);
}
}
Now stuff is easy, I have to write a builder that takes request URI as a string and returns mock data, as a String! Brilliant!
现在事情很简单,我必须编写一个构建器,它将请求 URI 作为字符串并返回模拟数据,作为字符串!杰出的!
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());
It works fine! .... For the first request.
After the first request (/stuff/1.json) my MockRestServiceServer responds with message "Assertion error: no further requests expected".
Basically, I can make as many requests to that MockRestServiceServer as there were .expect() calls on it. And since I had only 1 of them, only first request will go through.
Is there a way around it? I really don't want to mock service C 10 or 20 times...
它工作正常!.... 对于第一个请求。
在第一个请求 (/stuff/1.json) 之后,我的 MockRestServiceServer 响应消息“断言错误:不需要进一步的请求”。
基本上,我可以向该 MockRestServiceServer 发出与 .expect() 调用一样多的请求。因为我只有其中一个,所以只有第一个请求会通过。
有办法解决吗?我真的不想模拟服务 C 10 或 20 次......
回答by emeraldjava
If you look at the MockRestServiceServer class, it supports two 'expect()' methods. The first defaults to 'ExpectedCount.once()' but the second method allows you change this value
如果您查看 MockRestServiceServer 类,它支持两个“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);
}
I found this ticket MockRestServiceServer should allow for an expectation to occur multiple timeswhich outlines some options for second method.
我发现这张票MockRestServiceServer 应该允许多次发生,其中概述了第二种方法的一些选项。
In your case I think adding static import and using the manyTimes() method is neater code than the for loop
在您的情况下,我认为添加静态导入并使用 manyTimes() 方法是比 for 循环更简洁的代码
MockRestServiceServer
.expect(manyTimes(), requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
Other options are
其他选项是
once();
manyTimes();
times(5);
min(2);
max(8);
between(3,6);
回答by rapasoft
EDIT: See answer from @emeraldjava which shows the correct solution for Spring 4.3+ users.
编辑:请参阅@emeraldjava 的答案,其中显示了适用于 Spring 4.3+ 用户的正确解决方案。
Unfortunately there isn't any nicemechanism to expect multiple calls. You either do it manually or use loops, e.g.:
不幸的是,没有任何好的机制可以期待多次调用。您可以手动执行或使用循环,例如:
for (int i = 0; i < 10; i++) {
mockRestServiceServer
.expect(requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
.andRespond(withAutoDetectedCannedData());
}
Be aware that the requests must be called without any interruptions, e.g. there cannot be another REST call that doesn't match the "/stuff" URI.
请注意,必须在没有任何中断的情况下调用请求,例如,不能有另一个与“/stuff”URI 不匹配的 REST 调用。