Spring MockRestServiceServer, обрабатывающий несколько запросов к одному и тому же URI (автообнаружение)
Допустим, я пишу интеграционные тесты Spring для службы REST A. Эта служба, в свою очередь, обращается к другой службе REST B и получает список URI для обращения к службе REST C. Это своего рода шаблон автоматического обнаружения. Я хочу смоделировать ответы B и C, используя MockRestServiceServer.
Теперь ответ от B представляет собой список URI, все они очень похожи, и для примера скажем, что мой ответ от B выглядит примерно так:
{
uris: ["/stuff/1.json", "/stuff/2.json", "/stuff/39.json", "/stuff/47.json"]
}
Просто служба A добавит каждый из них к базовому URL для службы C и выполнит эти запросы.
Дразнить В легко, так как это всего 1 запрос.
Mocking 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 запроса в виде строки и возвращает фиктивные данные в виде строки! Brilliant!
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() для него. И так как у меня было только 1 из них, только первый запрос пройдет.
Есть ли способ обойти это? Я действительно не хочу издеваться над сервисом С 10 или 20 раз...
3 ответа
Если вы посмотрите на класс MockRestServiceServer, он поддерживает два метода "wait()". Первый по умолчанию равен "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);
РЕДАКТИРОВАТЬ: см. Ответ @emeraldjava, который показывает правильное решение для пользователей Spring 4.3+.
К сожалению, не существует какого-либо приятного механизма ожидания нескольких звонков. Вы делаете это вручную или используете циклы, например:
for (int i = 0; i < 10; i++) {
mockRestServiceServer
.expect(requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
.andRespond(withAutoDetectedCannedData());
}
Помните, что запросы должны вызываться без каких-либо прерываний, например, не может быть другого вызова REST, который не соответствует URI "/ stuff".
Для меня ситуация была немного иной. В моем тестовом классе у меня было два вспомогательных метода, которые обрабатывали запрос-заглушку. Поскольку один из этих методов находился после другого, я получил следующую ошибку:
Cannot add more expectations after actual requests are made.
Что решило для меня ошибку, так это создание нового макетаockRestServiceServer в каждом из методов:
Например, у меня возникла ошибка:
public void testStubRequestMethodOne(String expectedURL){
mockRestServiceServer.expect(requestTo(basePath + expectedURL))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess());
api.performPost(parameter1, parameter2)
mockRestServiceServer.verify();
mockRestServiceServer.reset();
}
// SERIES OF UNIT TESTS //
public void testStubRequestMethodTwo(String expectedURL, String extraURLPart){
mockRestServiceServer.expect(requestTo(basePath + expectedURL + extraURLPart))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess());
api.performPost(parameter1, parameter2)
mockRestServiceServer.verify();
mockRestServiceServer.reset();
}
По сути, ожидания были заявлены 2 раза в одном тестовом классе.
Что решило проблему, так это изменение этих вспомогательных методов на:
public void testStubRequestMethodOne(String expectedURL){
mockRestServiceServer = MockRestServiceServer.bindTo(restTemplate).build(); /*<-- this is new*/
mockRestServiceServer.expect(requestTo(basePath + expectedURL))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess());
api.performPost(parameter1, parameter2)
mockRestServiceServer.verify();
mockRestServiceServer.reset();
}
// SERIES OF UNIT TESTS //
public void testStubRequestMethodTwo(String expectedURL, String extraURLPart){
mockRestServiceServer = MockRestServiceServer.bindTo(restTemplate).build(); /*<-- this is new*/
mockRestServiceServer.expect(requestTo(basePath + expectedURL + extraURLPart))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess());
api.performPost(parameter1, parameter2)
mockRestServiceServer.verify();
mockRestServiceServer.reset();
}