Spring MockRestServiceServer处理对同一URI的多个请求(自动发现)


问题内容

假设我正在为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,而不是完整的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作为字符串并以字符串形式返回模拟数据!辉煌!

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响应并显示消息“断言错误:预期没有其他请求”。
基本上,我可以向该MockRestServiceServer发出与对它的.expect()调用一样多的请求。由于我只有其中一个,因此只有第一个请求会通过。
有办法解决吗?我真的不想模拟服务C 10或20次…


问题答案:

如果您查看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);
}

我发现此票证MockRestServiceServer应该允许多次出现期望,其中概述了第二种方法的一些选项。

在您的情况下,我认为添加静态导入并使用manyTimes()方法比for循环更整洁。

MockRestServiceServer
            .expect(manyTimes(), requestContainsUri("/stuff"))
            .andExpect(method(HttpMethod.GET))

其他选项是

once();
manyTimes();
times(5);
min(2);
max(8);
between(3,6);