You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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()保证请求链继续执行,这个点很容易漏测。
  • 用Mockito模拟Servlet相关的依赖(FilterConfig、ServletRequest等),避免启动容器,保证单元测试的独立性和速度。

内容的提问来源于stack exchange,提问作者H-Bar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 18:01:29