如何为Spring项目中的HSTSFilter类编写单元测试?
HSTSFilter 单元测试方案(JUnit 5 + Mockito)
直接上可落地的测试代码,覆盖这个Filter的初始化逻辑和核心过滤逻辑:
先确认依赖
确保你的pom.xml(Maven)或build.gradle里有这些测试依赖:
<!-- Maven示例 --> <dependencies> <!-- JUnit 5 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <!-- Mockito --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency> <!-- Servlet API 测试依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>test</scope> </dependency> </dependencies>
测试类实现
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.junit.jupiter.MockitoExtension; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class HSTSFilterTest { private HSTSFilter hstsFilter; @Mock private FilterConfig filterConfig; @Mock private ServletRequest servletRequest; @Mock private ServletResponse servletResponse; @Mock private HttpServletResponse httpServletResponse; @Mock private FilterChain filterChain; @BeforeEach void setUp() { hstsFilter = new HSTSFilter(); } // 测试正常初始化:maxAge合法,包含子域名 @Test void init_withValidMaxAgeAndIncludeSubDomains() throws ServletException { when(filterConfig.getInitParameter("maxAgeSeconds")).thenReturn("31536000"); when(filterConfig.getInitParameter("includeSubDomains")).thenReturn("true"); hstsFilter.init(filterConfig); assertEquals("max-age=31536000 ; includeSubDomains", hstsFilter.directives); } // 测试正常初始化:maxAge合法,不包含子域名 @Test void init_withValidMaxAgeWithoutSubDomains() throws ServletException { when(filterConfig.getInitParameter("maxAgeSeconds")).thenReturn("86400"); when(filterConfig.getInitParameter("includeSubDomains")).thenReturn("false"); hstsFilter.init(filterConfig); assertEquals("max-age=86400", hstsFilter.directives); } // 测试初始化时maxAge不合法(<=0),抛出异常 @Test void init_withInvalidMaxAge_throwsServletException() { when(filterConfig.getInitParameter("maxAgeSeconds")).thenReturn("-100"); ServletException exception = assertThrows(ServletException.class, () -> hstsFilter.init(filterConfig)); assertEquals("Invalid maxAgeSeconds value :: -100", exception.getMessage()); } // 测试安全请求(HTTPS),添加HSTS头 @Test void doFilter_secureRequest_addsHstsHeader() throws IOException, ServletException { when(filterConfig.getInitParameter("maxAgeSeconds")).thenReturn("31536000"); when(filterConfig.getInitParameter("includeSubDomains")).thenReturn("true"); hstsFilter.init(filterConfig); when(servletRequest.isSecure()).thenReturn(true); ServletResponse response = mock(HttpServletResponse.class); hstsFilter.doFilter(servletRequest, response, filterChain); verify((HttpServletResponse) response).addHeader("Strict-Transport-Security", "max-age=31536000 ; includeSubDomains"); verify(filterChain).doFilter(servletRequest, response); } // 测试非安全请求(HTTP),不添加HSTS头 @Test void doFilter_nonSecureRequest_doesNotAddHeader() throws IOException, ServletException { hstsFilter.init(filterConfig); when(servletRequest.isSecure()).thenReturn(false); hstsFilter.doFilter(servletRequest, servletResponse, filterChain); verify(httpServletResponse, never()).addHeader(anyString(), anyString()); verify(filterChain).doFilter(servletRequest, servletResponse); } // 测试安全请求,但响应不是HttpServletResponse,不添加头 @Test void doFilter_secureRequest_nonHttpResponse_doesNotAddHeader() throws IOException, ServletException { hstsFilter.init(filterConfig); when(servletRequest.isSecure()).thenReturn(true); when(servletResponse instanceof HttpServletResponse).thenReturn(false); hstsFilter.doFilter(servletRequest, servletResponse, filterChain); verify(httpServletResponse, never()).addHeader(anyString(), anyString()); verify(filterChain).doFilter(servletRequest, servletResponse); } }
关键测试点说明
- 初始化逻辑:覆盖合法参数、非法参数(maxAge<=0)的场景,确保参数解析和directives拼接正确,异常抛出符合预期。
- 过滤逻辑:
- 只有当请求是HTTPS(
isSecure=true)且响应是HttpServletResponse时,才添加HSTS头。 - 无论是否添加头,都必须调用
FilterChain.doFilter()保证请求链继续执行,这个点很容易漏测。
- 只有当请求是HTTPS(
- 用Mockito模拟Servlet相关的依赖(FilterConfig、ServletRequest等),避免启动容器,保证单元测试的独立性和速度。
内容的提问来源于stack exchange,提问作者H-Bar
相关产品推荐
相关产品推荐

