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

标签 spring spring-mvc mocking integration-testing

假设我正在为 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 作为字符串并将模拟数据作为字符串返回的构建器!太棒了!

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 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);

关于Spring MockRestServiceServer 处理对同一 URI 的多个请求(自动发现),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30713734/

相关文章:

java - Spring 与 Hibernate JPA - EntityManager 已关闭异常

spring - 否定 Spring Controller 的 RequestMapping 中的参数

unit-testing - CakePHP 2.1:使用 Session::read() 制作模拟 Controller

c++ - 如何使用 google test/mock 测试基于 MFC CWnd 的类?

spring - 带有路径变量的 Thymeleaf 形式

node.js - Angular HttpClient 代理失败

java - 为什么在我的 JBoss Tomcat 7 + Spring MVC 项目中出现 HTTP Status 404?

html - 将视频从 Controller 流式传输到 html5 视频播放器

c# - 如何模拟 Web 服务

java - Spring MVC 中整数(最终类)的 CGLib 代理