Mockito测试HttpFilter的doFilter方法覆盖率为0%的问题排查与测试方案
问题
为Java的HttpFilter编写JUnit+Mockito测试,已编写doFilter()的测试代码,但代码覆盖率显示方法和行覆盖率均为0%。doFilter方法返回void,且req会被重新赋值为新实例,因此将断言放在FilterChain方法中,验证传入的请求实例是否符合预期。请问原因是什么?如何编写正确的测试?
待测试的XSSFilter代码:
public class XSSFilter implements Filter{ public static final String XSS_FILTER_ACTIVE_PROPERTY_KEY = "XSSFilterActive"; public static final String XSS_FILTER_ACTIVE_DEFAULT_VALUE = "false"; private Logger logger; boolean isXssFilterActive= false; @Override public void init(FilterConfig filterConfig) throws ServletException { if (logger==null) { logger = LoggerFactory.getLogger(this.getClass().getName()); } isXssFilterActive = Boolean.parseBoolean(ResourceManagerListener.getProperty(XSS_FILTER_ACTIVE_PROPERTY_KEY, XSS_FILTER_ACTIVE_DEFAULT_VALUE)); logger.info("isXssFilterActive ={}", isXssFilterActive); if (isXssFilterActive) { logger.info("filter XSS on"); } } @Override public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; if (isXssFilterActive) { req = new XSSRequestWrapper(request); } chain.doFilter(req, response); } @Override public void destroy() { //empty } }
现有测试代码:
public class XssFilterTest { @Mock HttpServletRequest req; @Mock ServletResponse response; private AutoCloseable autoCloseableMock; @Before public void setUp() throws Exception { autoCloseableMock = MockitoAnnotations.openMocks(this); } //test XSS filtering ON: test request instance of XSSRequestWrapper @Test public void testXSSFilterActiveTrue() throws Exception { final MockedStatic<ResourceManagerListener> resourceManagerListenerMockedStatic = mockStatic(ResourceManagerListener.class); when(ResourceManagerListener.getProperty("XSSFilterActive", "false")).thenReturn("true"); XSSFilter target = new XSSFilter(); FilterConfig config = mock(FilterConfig.class); target.init(config); FilterChain filterChainConsumer = ( request, response) -> assertThat(request.getClass()).as("check filter is on").isEqualTo(XSSRequestWrapper.class); HttpServletRequest request = new HttpServletRequestWrapper(req); target.doFilter(request,response, filterChainConsumer); resourceManagerListenerMockedStatic.close(); } //XSS filtering off: test request instanceOf HttpServletRequestWrapper @Test public void testXSSFilterActiveFalse() throws Exception { //test quando spento final MockedStatic<ResourceManagerListener> resourceManagerListenerMockedStatic = mockStatic(ResourceManagerListener.class); when(ResourceManagerListener.getProperty("XSSFilterActive", "false")).thenReturn("false"); XSSFilter target = new XSSFilter(); FilterConfig config = mock(FilterConfig.class); target.init(config); FilterChain filterChainConsumer = ( request, response) -> assertThat(request.getClass()).as("check filter is off").isEqualTo(HttpServletRequestWrapper.class); HttpServletRequest request = new HttpServletRequestWrapper(req); target.doFilter(request,response, filterChainConsumer); resourceManagerListenerMockedStatic.close(); } @After public void tearDown() throws Exception { autoCloseableMock.close(); } }
覆盖率为0%的可能原因
- 测试类与被测试类的路径配置问题:如果覆盖率工具(如JaCoCo)的扫描范围未包含
XSSFilter所在的包,或者测试类未被识别为有效测试用例,会导致覆盖率统计为0。 - 测试未实际执行被测试方法:若测试类未被JUnit框架正确执行(如缺少
@Test注解、未使用Mockito运行器),会导致被测试类代码未被调用,覆盖率为0。 - 静态Mock的资源未正确生效:如果静态Mock的时机晚于被测试类的初始化,或者
ResourceManagerListener.getProperty的调用未被正确拦截,会导致init方法未按预期设置isXssFilterActive,进而doFilter的分支未被执行,但这种情况一般不会导致全0覆盖率。 - 覆盖率工具的缓存或配置错误:部分覆盖率工具可能存在缓存,或者配置文件中排除了被测试类,导致统计结果异常。
修正后的测试代码
以下是优化后的测试代码,解决覆盖率问题并提升测试规范性:
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; import static org.mockito.Mockito.*; import static org.assertj.core.api.Assertions.assertThat; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; @ExtendWith(MockitoExtension.class) public class XSSFilterTest { @Mock private HttpServletRequest mockHttpReq; @Mock private ServletResponse mockResponse; @Mock private FilterChain mockFilterChain; @Mock private FilterConfig mockFilterConfig; private XSSFilter xssFilter; private MockedStatic<ResourceManagerListener> resourceManagerStaticMock; @BeforeEach void setUp() { xssFilter = new XSSFilter(); // Mock日志工厂,避免测试时输出日志并确保init方法正常执行 try (MockedStatic<LoggerFactory> loggerFactoryMock = mockStatic(LoggerFactory.class)) { loggerFactoryMock.when(() -> LoggerFactory.getLogger(anyString())).thenReturn(mock(Logger.class)); xssFilter.init(mockFilterConfig); } } @Test void doFilter_whenXssFilterActive_shouldWrapRequestWithXSSRequestWrapper() throws Exception { // 激活XSS过滤配置 resourceManagerStaticMock = mockStatic(ResourceManagerListener.class); resourceManagerStaticMock.when(() -> ResourceManagerListener.getProperty(XSSFilter.XSS_FILTER_ACTIVE_PROPERTY_KEY, XSSFilter.XSS_FILTER_ACTIVE_DEFAULT_VALUE)) .thenReturn("true"); // 重新初始化过滤器,读取最新配置 xssFilter.init(mockFilterConfig); ServletRequest originalRequest = new HttpServletRequestWrapper(mockHttpReq); xssFilter.doFilter(originalRequest, mockResponse, mockFilterChain); // 验证FilterChain收到的是XSSRequestWrapper实例 verify(mockFilterChain).doFilter(argThat(req -> req instanceof XSSRequestWrapper), eq(mockResponse)); } @Test void doFilter_whenXssFilterInactive_shouldPassOriginalRequest() throws Exception { // 关闭XSS过滤配置 resourceManagerStaticMock = mockStatic(ResourceManagerListener.class); resourceManagerStaticMock.when(() -> ResourceManagerListener.getProperty(XSSFilter.XSS_FILTER_ACTIVE_PROPERTY_KEY, XSSFilter.XSS_FILTER_ACTIVE_DEFAULT_VALUE)) .thenReturn("false"); // 重新初始化过滤器,读取最新配置 xssFilter.init(mockFilterConfig); ServletRequest originalRequest = new HttpServletRequestWrapper(mockHttpReq); xssFilter.doFilter(originalRequest, mockResponse, mockFilterChain); // 验证FilterChain收到的是原始请求实例 verify(mockFilterChain).doFilter(eq(originalRequest), eq(mockResponse)); } @AfterEach void tearDown() { if (resourceManagerStaticMock != null) { resourceManagerStaticMock.close(); } } }
关键优化点
- 使用
@ExtendWith(MockitoExtension.class)简化Mock管理,无需手动初始化和关闭Mock资源。 - Mock
LoggerFactory避免测试过程中输出日志,同时确保init方法能正常执行。 - 使用Mockito的
verify和argThat替代自定义FilterChain,更符合测试规范,验证逻辑更清晰。 - 明确验证原始请求是否被直接传递(关闭过滤时),避免依赖类型断言的潜在问题。
- 确保静态Mock资源在测试后正确关闭,避免内存泄漏。
内容的提问来源于stack exchange,提问作者robyp7
相关产品推荐
相关产品推荐

