MockRestServiceServer как проверить только путь запроса, исключая строку запроса

#java #spring-boot #resttemplate #mockrestserviceserver

Вопрос:

У меня есть @Component DataClientImpl, который вызывает REST API с помощью RestTemplate . Конечная точка API имеет параметры запроса, которые передаются при вызове RestTemplate . Существует @RestClientTest тестовый класс DataApiClientImplTest , который тестирует DataClientImpl , издеваясь над ОСТАЛЬНЫМИ вызовами с помощью MockRestServiceServer .

В методе тестирования я хочу убедиться, что правильный путь к конечной точке и параметры запроса (в частности, имя) используются при вызове API. Использование MockRestRequestMatchers.requestTo() и MockRestRequestMatchers.queryParam() методы проверки.

При MockRestRequestMatchers.requestTo() запуске теста происходит сбой. Похоже, что он сравнивает фактический url-адрес, включающий строку запроса, с ожидаемым URL-адресом без строки запроса (который передается MockRestRequestMatchers.requestTo() методу.

В пом я использую

 lt;parentgt;  lt;groupIdgt;org.springframework.bootlt;/groupIdgt;  lt;artifactIdgt;spring-boot-starter-parentlt;/artifactIdgt;  lt;versiongt;2.6.0lt;/versiongt;  lt;relativePath/gt;  lt;/parentgt;  

Код ниже.

 @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();  }  }  

Ошибка в том, что

 java.lang.AssertionError: Unexpected request expected:lt;http://localhost:8080/testBucketId/objects/test-obj-idgt; but was:lt;http://localhost:8080/testBucketId/objects/test-obj-id?token=asdf12345amp;keyid=testKeyIdgt; Expected :http://localhost:8080/testBucketId/objects/test-obj-id Actual :http://localhost:8080/testBucketId/objects/test-obj-id?token=asdf12345amp;keyid=testKeyId  

Вместо этого я могу передать сопоставитель requestTo() методу. Но тогда какой смысл MockRestRequestMatchers.queryParam() в методе, когда весь URL-адрес с параметрами запроса должен быть проверен с помощью сопоставления.

Один из вариантов-использовать StartsWith Matcher MockRestRequestMatchers.requestTo(CoreMatchers.startsWith(OBJECT_ID_URI)) . Но это не то же самое, что проверка точного соответствия.

Пробовал с другими перегруженными версиями MockRestRequestMatchers.requestTo() метода. Они все ведут себя одинаково.

Есть ли другой лучший способ сделать точный путь к конечной точке только совпадающим.

Я не включил код класса испытуемого DataClientImpl , так как думал, что это выходит за рамки проблемы, но я могу добавить здесь, если понадобится.

Ответ №1:

Недавно я столкнулся с аналогичной проблемой и не нашел никакого решения со встроенными сопоставителями/методами. Я закончил тем, что написал свою собственную реализацию 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));  } }  

Вы можете использовать его так:

 mockServer  .expect(RequestPathMatcher.of(requestPath))  .andRespond(...)  

Комментарии:

1. @madhav-туранги: есть отзывы?

2. Фактическая проблема заключается в getURI().toString() методе. В MockRestRequestMatchers.requestTo() значительной степени это делает то же самое.

3. Да, но суть в том, что вы можете реализовать произвольную логику внутри переопределенного match метода, что вам нужно.

4. Я понимаю, в чем дело. Я ответил на использование getURI().toString() в вашем ответе. Неправильное понимание соответствия, заданного, MockRestRequestMatchers.requestTo() было корнем проблемы, главным образом с URI. При использовании Java 8 и выше эту работу выполняет лямбда, которую я обнаружил и добавил более подробную информацию ниже. Спасибо за ваше участие.

Ответ №2:

На тот случай, если кто — нибудь наткнется на эту ниточку. Ответ очень прост, но я добавлю больше подробностей о своих интересных находках, которые могут быть полезны другим.

TL;DR ответ:
Воспользуйся mockRestServiceServer.expect(request -gt; 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 -gt; assertEquals("Request URI", expectedUri, request.getURI().toString());  }  

Что я имею в виду под дизайном, java.net.URI так это то, что по определению является реализацией RFC 2396 и RFC 2732. Ниже приведен отрывок из java.net.URI javadoc

Представляет собой ссылку на Единый идентификатор ресурса (URI). Помимо некоторых незначительных отклонений, отмеченных ниже, экземпляр этого класса представляет ссылку URI, как определено RFC 2396: Унифицированные идентификаторы ресурсов (URI): Общий синтаксис, измененный RFC 2732: Формат для буквальных адресов IPv6 в URL.

Я не полностью прочитал RFC, но в дальнейшем javadoc упоминает синтаксис и компоненты URI

Иерархический URI подлежит дальнейшему анализу в соответствии с синтаксисом

[схема:][//полномочия][путь][?запрос][#фрагмент]

что указывает на то, что URI-это не просто путь запроса, а включает все эти части. Поэтому RequestMatcher возвращаемый RequestMatchers.requestTo(String uri) методом, который утверждает, как показано ниже

 assertEquals("Request URI", expectedUri, request.getURI().toString())  

сравнивает фактический URI, включая строку запроса, с ожидаемым uri. Если ожидаемая строка uri представляет собой только путь без строки запроса, а фактический запрос содержит параметры запроса, утверждение завершается ошибкой.

Итак, мое намерение сопоставить только путь запроса в expect() методе, поскольку параметры запроса утверждаются с помощью последующих andExpect() методов, может быть реализовано следующим образом

 mockRestServiceServer.expect(request -gt; assertEquals(OBJECT_ID_URI, request.getURI().getPath()))  

Используя java.net.URI#getPath() метод, который дает только часть пути фактического URI.

Еще одним важным моментом здесь RequestMatcher является функциональный интерфейс с методом

 void match(ClientHttpRequest request) throws IOException, AssertionError;  

Лямбда — выражение может быть передано expect() методу, который принимает ClientHttpRequest в качестве аргумента и ничего не возвращает (технически void ).

Полный метод тестирования приведен ниже и работает так, как ожидалось

 @Test  void testApiCall() {  mockRestServiceServer.expect(request -gt; 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();  }