MockRestServiceServer 如何仅验证不包括查询字符串的请求路径

mad*_*ngi 6 java resttemplate spring-boot mockrestserviceserver

我有一个@ComponentDataClientImpl,它使用RestTemplate. API端点具有查询参数,这些参数在调用时传递RestTemplate。有一个@RestClientTestTest 类DataApiClientImplTest测试DataClientImpl,使用 模拟 REST 调用MockRestServiceServer。

在测试方法中,我想验证 API 调用中是否使用了正确的端点路径和查询参数(特别是名称)。使用MockRestRequestMatchers.requestTo()和MockRestRequestMatchers.queryParam()方法进行验证。

MockRestRequestMatchers.requestTo()运行测试时失败。它似乎将包含查询字符串的实际 url 与不带查询字符串的预期 url(传递给MockRestRequestMatchers.requestTo()method.

在pom中,我正在使用

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.0</version>
    <relativePath/> 
</parent>
Run Code Online (Sandbox Code Playgroud)

代码如下。

@RestClientTest(DataApiClientImpl.class)
@AutoConfigureWebClient(registerRestTemplate = true)
class DataApiClientImplTest {

    private static final String OBJECT_ID_URI = "http://localhost:8080/testBucketId/objects/test-obj-id";

    @Autowired
    private DataApiClientImpl dataApiClientImpl;

    @Autowired
    private MockRestServiceServer mockRestServiceServer;

    @Test
    void testApiCall() {
        mockRestServiceServer.expect(MockRestRequestMatchers.requestTo(OBJECT_ID_URI))
                .andExpect(MockRestRequestMatchers.method(HttpMethod.GET))
                .andExpect(MockRestRequestMatchers.queryParam("keyid", CoreMatchers.anything()))
                .andExpect(MockRestRequestMatchers.queryParam("token", CoreMatchers.anything()))
                .andRespond(MockRestResponseCreators.withSuccess("dummy", MediaType.APPLICATION_JSON));

        String response = dataApiClientImpl.getItem("asdf12345", "test-obj-id");

        Assertions.assertThat(response).isNotNull();
    }

}
Run Code Online (Sandbox Code Playgroud)

错误是

java.lang.AssertionError: Unexpected request expected:<http://localhost:8080/testBucketId/objects/test-obj-id> but was:<http://localhost:8080/testBucketId/objects/test-obj-id?token=asdf12345&keyid=testKeyId>
Expected :http://localhost:8080/testBucketId/objects/test-obj-id
Actual   :http://localhost:8080/testBucketId/objects/test-obj-id?token=asdf12345&keyid=testKeyId
Run Code Online (Sandbox Code Playgroud)

我可以将 Matcher 传递给requestTo()方法。MockRestRequestMatchers.queryParam()但是,当必须使用 Matcher 验证带有查询参数的整个 url 时,该方法有什么用呢?

一种选择是使用startsWith Matcher MockRestRequestMatchers.requestTo(CoreMatchers.startsWith(OBJECT_ID_URI))。但它与精确匹配验证不同。

尝试使用其他重载版本的MockRestRequestMatchers.requestTo()方法。他们的行为都一样。

是否有其他更好的方法来执行精确的端点路径仅匹配。

我没有包含测试主题类代码,DataClientImpl因为我认为它超出了问题的范围,但如果需要,我可以在此处添加。

mad*_*ngi 7

以防万一有人偶然发现这个线程。答案很简单,但添加更多我有趣的发现的细节可能对其他人有帮助。

TL;DR 答案:
使用mockRestServiceServer.expect(request -> assertEquals(OBJECT_ID_URI, request.getURI().getPath()))

有趣的发现:
看来,它是根据设计MockRestRequestMatchers.requestTo(String uri)与 uri(包括查询字符串)进行匹配的。以下是该方法的实现摘录,

    public static RequestMatcher requestTo(String expectedUri) {
        Assert.notNull(expectedUri, "'uri' must not be null");
        return request -> assertEquals("Request URI", expectedUri, request.getURI().toString());
    }
Run Code Online (Sandbox Code Playgroud)

我所说的设计的意思是,根据定义是 RFC 2396 和 RFC 2732 的实现。下面是javadocjava.net.URI的摘录java.net.URI

表示统一资源标识符 (URI) 引用。除了下面提到的一些细微偏差之外,此类的实例表示 RFC 2396 定义的 URI 引用:统一资源标识符 (URI):通用语法,并由 RFC 2732:URL 中的文字 IPv6 地址格式进行修订。

我还没有完全阅读 RFC,但 javadoc 在URI 语法和组件下进一步提到

分层的 URI 需要根据语法进一步解析

[方案:][//权限][路径][?查询][#fragment]

这表明URI不仅仅是请求路径,而是包括所有这些部分。因此,断言方法RequestMatcher返回的RequestMatchers.requestTo(String uri)结果如下

assertEquals("Request URI", expectedUri, request.getURI().toString())
Run Code Online (Sandbox Code Playgroud)

将包括查询字符串的实际 URI 与预期 URI 进行比较。当期望的 uri 字符串仅是路径而没有查询字符串并且实际请求包含查询参数时,断言失败。

因此,我的意图是仅匹配方法中的请求路径expect(),因为查询参数是通过后续andExpect()方法断言的,可以按如下方式实现

mockRestServiceServer.expect(request -> assertEquals(OBJECT_ID_URI, request.getURI().getPath()))
Run Code Online (Sandbox Code Playgroud)

使用java.net.URI#getPath()方法,仅给出实际 URI 的路径部分。

这里的另一个重要点是,它RequestMatcher是一个带有方法的功能接口

void match(ClientHttpRequest request) throws IOException, AssertionError;
Run Code Online (Sandbox Code Playgroud)

lambda 表达式可以传递给以参数为参数且不返回任何内容expect()的方法(从技术上讲)。ClientHttpRequestvoid

完整的测试方法如下并且按预期工作

    @Test
    void testApiCall() {
        mockRestServiceServer.expect(request -> assertEquals(OBJECT_ID_URI, request.getURI().getPath()))
                .andExpect(MockRestRequestMatchers.queryParam("keyid", CoreMatchers.anything()))
                .andExpect(MockRestRequestMatchers.queryParam("token", CoreMatchers.anything()))
                .andRespond(MockRestResponseCreators.withSuccess("dummy", MediaType.APPLICATION_JSON));

        String response = dataApiClientImpl.getItem("asdf12345", "test-obj-id");

        Assertions.assertThat(response).isNotNull();
    }

Run Code Online (Sandbox Code Playgroud)


eol*_*eol 0

我最近遇到了类似的问题,没有找到任何内置匹配器/方法的解决方案。我最终编写了自己的 Matcher 实现,如下所示:

public class RequestPathMatcher implements RequestMatcher {
    private final String expectedRequestPath;

    public static RequestPathMatcher of(String expectedRequestPath) {
        return new RequestContainsUriMatcher(expectedRequestPath);
    }

    private RequestPathMatcher(String expectedRequestPath) {
        this.expectedRequestPath = expectedRequestPath;
    }

    @Override
    public void match(ClientHttpRequest clientHttpRequest) throws AssertionError {
        final var actualRequestPath = clientHttpRequest.getURI().toString();
        var matchResult = false;
        // your match logic here
        assertTrue(matchResult , String.format("Expected %s to have request path%s", actualRequestPath, expectedRequestPath));
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用它:

 mockServer
      .expect(RequestPathMatcher.of(requestPath))
      .andRespond(...)
Run Code Online (Sandbox Code Playgroud)