
本文详细讲解如何为 spring boot 中使用 caffeine 缓存实现每秒请求数限制的自定义 filter 编写可靠、可验证的单元测试,涵盖依赖注入、mock 对象配置、http 请求模拟及状态断言等关键环节。
本文详细讲解如何为 spring boot 中使用 caffeine 缓存实现每秒请求数限制的自定义 filter 编写可靠、可验证的单元测试,涵盖依赖注入、mock 对象配置、http 请求模拟及状态断言等关键环节。
在 Spring Boot 应用中,通过 Filter 实现客户端 IP 级别的请求速率限制(如每秒最多 N 次)是一种轻量且通用的做法。但其测试难点在于:RequestFilter 内部依赖 LoadingCache 和真实 HTTP 上下文(HttpServletRequest/HttpServletResponse),直接对私有缓存字段打桩(如 when(filter.requestCountsPerIpAddress.get(...)))不可行——因为 requestCountsPerIpAddress 是 final 字段且由构造器初始化,无法被 Mockito 代理或重写。
✅ 正确的测试策略是以集成式单元测试思路驱动:不 mock 被测类本身,而是:
- 保留真实 Filter 实例:通过 @SpringBootTest 或 @ContextConfiguration 加载配置(含 @Value 属性),或手动构造并注入所需依赖;
- Mock Servlet API 接口:使用 Mockito 模拟 HttpServletRequest、HttpServletResponse 和 FilterChain;
- 控制请求上下文:显式设置 X-Forwarded-For 头或 getRemoteAddr() 返回值,确保 getClientIP() 行为可预测;
- 触发完整 doFilter 流程:调用 filter.doFilter() 多次,验证响应状态码与内容是否符合限流逻辑。
以下为推荐的、可直接运行的测试示例(基于 JUnit 5 + Mockito):
@SpringBootTest
class RequestFilterTest {
@Autowired
private RequestFilter filter; // 真实 Bean,自动注入配置属性
@Test
void requestFilterMaxRequestExceededTest() throws Exception {
// Mock Servlet 对象
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
FilterChain chain = mock(FilterChain.class);
PrintWriter writer = new StringWriter();
when(response.getWriter()).thenReturn(new PrintWriter(writer));
String clientIp = "192.168.1.100";
// 模拟 X-Forwarded-For 头(或 fallback 到 remote addr)
when(request.getHeader("X-Forwarded-For")).thenReturn(null);
when(request.getRemoteAddr()).thenReturn(clientIp);
// 执行 10 次合法请求(假设 MAX_REQUESTS_PER_SECOND = 10)
for (int i = 0; i < 10; i++) {
filter.doFilter(request, response, chain);
verify(chain, times(i + 1)).doFilter(request, response); // 验证未拦截
reset(chain, response);
}
// 第 11 次应触发限流
filter.doFilter(request, response, chain);
verify(response).setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
verify(response).getWriter();
assertThat(writer.toString()).contains("Too many requests");
}
}? 关键注意事项:
- ✅ 必须确保 application-test.yml 中配置了 chatGPT.max.request.per.second: 10,否则 @Value 注入失败;
- ✅ 不要尝试 @Mock RequestFilter —— 这会导致 requestCountsPerIpAddress 为 null,且绕过实际业务逻辑;
- ✅ Caffeine Cache 在测试中无需 mock:它天然支持时间控制(expireAfterWrite(1, SECONDS)),多次调用会真实触发计数+过期逻辑;
- ⚠️ 若需精确控制缓存行为(如跳过过期),可改用 Caffeine.newBuilder().ticker(Ticker.systemTicker()).build(...) 并在测试中注入自定义 Ticker,但通常非必需;
- ✅ 建议补充边界测试:空 IP、多级代理头(X-Forwarded-For: a,b,c)、非法请求类型等。
总结:Filter 单元测试的核心原则是「测行为,而非测字段」。聚焦于 doFilter() 的输入输出契约——给定特定请求,是否返回预期状态码与响应体。通过真实 Filter 实例 + 精准 Mock Servlet 接口,即可覆盖全部业务路径,兼顾可靠性与可维护性。

















