2015-06-08 4 views
9

Предположим, что я пишу тесты интеграции Spring для службы REST A. Эта служба, в свою очередь, попадает в другую службу REST B и получает список URI, которые попадают в REST service C. Это своего рода шаблон автоматического обнаружения. Я хочу издеваться над ответами B и C с помощью MockRestServiceServer.
Теперь ответ от B список URI, они все очень похожи, и для примера позволяет сказать, что мой ответ от B, как так:Spring MockRestServiceServer обрабатывает несколько запросов к одному и тому же URI (автообнаружение)

{ 
    uris: ["/stuff/1.json", "/stuff/2.json", "/stuff/39.json", "/stuff/47.json"] 
} 

Просто обслуживание А будет добавлять каждый из их на базовый URL для службы C и сделать эти запросы.
Mocking B легко, так как это всего лишь 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); 
    } 
} 

Теперь материал легко, я ВГ e, чтобы написать строитель, который принимает URI запроса как строку и возвращает mock данные, как String! 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 из них, только первый запрос будет проходить.
Есть ли способ вокруг него? Я действительно не хочу издеваться над сервисом C 10 или 20 раз ...

+0

Благодарность за реализацию RequestContainsUriMatcher – Silentbang

ответ

14

Если вы посмотрите на класс MockRestServiceServer, он поддерживает два метода ожидания(). Первые по умолчанию «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() метод аккуратнее кода, чем для цикла

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

Другие варианты

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

Реализовано весной 4.3 – aurelije

+1

Теперь это правильный ответ и должен быть выбран как таковой. –

8

EDIT: см. Ответ от @emeraldjava, в котором показано правильное решение для пользователей Spring 4.3+.

К сожалению, нет хороший механизм для ожидания нескольких вызовов. Вы либо сделать это вручную или использовать циклы, например .:

for (int i = 0; i < 10; i++) {   
     mockRestServiceServer 
       .expect(requestContainsUri("/stuff")) 
       .andExpect(method(HttpMethod.GET)) 
       .andRespond(withAutoDetectedCannedData()); 
} 

Имейте в виду, что запросы должны быть вызваны без каких-либо перерывов, например, не может быть другого вызова REST, который не соответствует URI «/ stuff».

+0

Петля решает его. Спасибо – Silentbang

+0

Как только ожидание будет проверено, вы не сможете добавить дополнительные ожидания. 'Невозможно добавить ожидаемые запросы с уже запущенным тестом –