
本文详解如何为 spring boot 中使用 caffeine 缓存实现的 ip 级每秒请求数限制 filter 编写可靠、可验证的单元测试,涵盖依赖注入、mock 对象配置、真实 filter 实例调用及响应断言等核心实践。
本文详解如何为 spring boot 中使用 caffeine 缓存实现的 ip 级每秒请求数限制 filter 编写可靠、可验证的单元测试,涵盖依赖注入、mock 对象配置、真实 filter 实例调用及响应断言等核心实践。
在 Spring Boot 应用中,通过 Filter 实现轻量级请求限流(如基于 IP 的 QPS 控制)是一种常见且高效的做法。但其单元测试常因涉及 LoadingCache、HttpServletRequest/HttpServletResponse 生命周期及 FilterChain 调用链而难以覆盖。关键误区在于试图对被测类(RequestFilter)内部字段(如 requestCountsPerIpAddress)进行 mock —— 这不仅违反面向对象封装原则,更会导致缓存行为失效(Caffeine 的 get() 是计算式调用,mock 后无法触发实际计数逻辑)。
✅ 正确做法是:使用真实 Filter 实例 + 受控的 Mock 请求/响应对象,让业务逻辑(包括缓存更新、状态判断、HTTP 响应写入)完整执行。
1. 准备测试环境
首先确保测试资源配置生效。在 src/test/resources/application.yml 中定义属性:
chatGPT:
max:
request:
per:
second: 10
同时,启用 Spring Test 上下文以支持 @Autowired 注入:
@SpringBootTest
@Import(RequestFilter.class) // 显式导入,避免组件扫描遗漏
class RequestFilterTest {
@Autowired
private RequestFilter filter;
@Mock
private ServletRequest servletRequest;
@Mock
private ServletResponse servletResponse;
@Mock
private FilterChain filterChain;
@Mock
private HttpServletRequest httpServletRequest;
@Mock
private HttpServletResponse httpServletResponse;
}
⚠️ 注意:@Mock 必须配合 @ExtendWith(MockitoExtension.class)(JUnit 5)或 @RunWith(MockitoJUnitRunner.class)(JUnit 4),且 servletRequest 需向下转型为 HttpServletRequest,因此需额外 mock httpServletRequest 并在 when() 中返回它。
2. 构建可复现的测试场景
核心测试逻辑应模拟 连续发起超过阈值的请求,并验证第 N+1 次请求是否返回 429 Too Many Requests:
@Test
@DisplayName("当同一IP请求超限时,返回429状态码")
void requestFilterMaxRequestExceededTest() throws IOException, ServletException {
// 给定:模拟客户端IP
String clientIp = "192.168.1.100";
// 配置 HttpServletRequest 返回指定IP和X-Forwarded-For头(可选)
when(servletRequest).thenReturn(httpServletRequest);
when(httpServletRequest.getRemoteAddr()).thenReturn(clientIp);
when(httpServletRequest.getHeader("X-Forwarded-For")).thenReturn(null); // 不走代理
// 配置 HttpServletResponse
when(servletResponse).thenReturn(httpServletResponse);
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
when(httpServletResponse.getWriter()).thenReturn(printWriter);
// 执行12次请求(阈值为10,第11、12次应触发限流)
for (int i = 0; i <h3>3. 关键注意事项</h3>
- 不要 mock LoadingCache:Caffeine 的 get(key) 会触发 CacheLoader(此处为 key -> 0),mock 后将跳过计数逻辑。测试必须依赖真实缓存行为。
- 避免 @MockBean 替代 @Autowired:@MockBean 会替换 Spring 容器中的 Bean,导致 Filter 失去真实缓存实例,限流逻辑失效。
- 时间敏感性处理:Caffeine 设置了 expireAfterWrite(1, SECONDS),测试中若间隔过长可能触发自动过期。建议在测试前后调用 invalidateAll() 清理,或使用 Ticker 注入固定时钟(进阶)。
- getClientIP() 的健壮性:测试应覆盖 X-Forwarded-For 存在/不存在两种情况,确保 IP 解析逻辑正确。
总结
成功的 Filter 单元测试 = 真实 Bean 实例 + 精准 Mock 输入输出 + 端到端行为验证。与其绕过缓存逻辑做“伪测试”,不如构造可控的请求序列,让限流规则在真实缓存与响应机制下接受检验。这不仅能验证功能正确性,更能暴露并发、过期、IP 解析等边界问题,是保障网关层稳定性的重要防线。










