#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 ошибки
- Ваш фильтр не заключает ответ в
ContentCachingResponseWrapper
- Вы пишете ответ до того, как в базовом ответе произошла обертка, поэтому
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);
}
Что-то в этом роде должно сработать.