ContentCachingResponseWrapper.getContentAsByteArray() пуст при тестировании с помощью MockHttpServletResponse

#java #spring-boot #unit-testing #junit #servlet-filters

Вопрос:

У меня есть фильтр для регистрации некоторой информации для каждого запроса в приложение Spring Boot. Часть этой информации мне нужно извлечь из тела. Это не проблема сама по себе, но для этого я использую ContentCachingResponseWrapper , и это портит мои модульные тесты.

Вот упрощенная версия моего фильтра:

 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
        var wrappedResponse = response instanceof ContentCachingResponseWrapper ? (ContentCachingResponseWrapper) response : new ContentCachingResponseWrapper(response);
        filterChain.doFilter(request, wrappedResponse);
    } finally {        
        System.out.println("Response body: "   new String(wrappedResponse.getContentAsByteArray()));
        wrappedResponse.copyBodyToResponse();
    }
}
 

А вот упрощенная версия моего теста:

     void myTest() throws ServletException, IOException {
        final String body = "This is a body that my service might return.";
        var testResp = new MockHttpServletResponse();
        testResp.getWriter().print(body);
        testResp.getWriter().flush();
        testResp.setContentLength(body.length());

        myFilter.doFilterInternal(Mockito.mock(HttpServletRequest.class), testResp, Mockito.mock(FilterChain.class));
    }
 

Проблема в том, что при запуске моих тестов wrappedResponse.getContentAsByteArray() возвращается пустой массив.

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

1. И почему это должно быть так? Ответ не завернут, по крайней мере, ваш фильтр ничего не упаковывает. Так что не уверен, как вы думаете, это сработает.

2. @M. Deinum Не будет ли первая строка в блоке finally заключать ответ в оболочку ContentCachingResponseWrapper?

3. Нет, он проверяет, является ли он экземпляром этого класса, и оборачивается после факта. Ответ должен быть завернут перед вызовом filterchain.doFilter и передан в качестве ответа. Затем завернутый ответ следует использовать в finally блоке.

4. Спасибо. Я, вероятно, переместил некоторые вещи во время отладки, потому что это работало раньше (когда не тестировалось). Я изменил его сейчас, и поведение во время тестирования остается прежним.

5. Чего тоже следовало ожидать. Поскольку вы пишете ответ до ContentCachingResponseWrapper того, как он успеет его перехватить и кэшировать.

Ответ №1:

В вашем коде есть 2 ошибки

  1. Ваш фильтр не заключает ответ в ContentCachingResponseWrapper
  2. Вы пишете ответ до того, как в базовом ответе произошла обертка, поэтому ContentCachingResponseWrapper кэширование ответа не изменилось.
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
        var wrappedResponse = response instanceof ContentCachingResponseWrapper ? (ContentCachingResponseWrapper) response : new ContentCachingResponseWrapper(response);
        filterChain.doFilter(request, wrappedResponse);
    } finally {        
        System.out.println("Response body: "   new String(wrappedResponse.getContentAsByteArray()));
        wrappedResponse.copyBodyToResponse();
    }
}
 

Теперь ответ будет завернут в обертку для ответов, записанных ниже FilterChain . Это также то, что вы можете использовать в своем тестовом наборе, высмеивая FilterChain и записывая ответ в ответе.

 void myTest() throws ServletException, IOException {
  var body = "This is a body that my service might return.";
  var req = new MockHttpServletRequest();
  var res = new MockHttpServletResponse();
  var mockChain = Mockito.mock(FilterChain.class);
  Mockito.when(mockChain.doFilter(any(), any())
    .thenAnswer((it -> {
      var response = it.getArgument(1, HttpServletResponse.class);
      response.getWriter().print(body);
      response.getWriter().flush();
      response.setContentLength(body.length());
      return null;      
     });
   myFilter.doFilterInternal(req, res, mockChain);
}
 

Что-то в этом роде должно сработать.