
本文详解如何对 spring boot 中使用 caffeine 缓存实现的 ip 级每秒请求数限制 filter 进行真实、可验证的单元测试,涵盖配置注入、依赖模拟、多轮请求触发与响应断言等关键实践。
本文详解如何对 spring boot 中使用 caffeine 缓存实现的 ip 级每秒请求数限制 filter 进行真实、可验证的单元测试,涵盖配置注入、依赖模拟、多轮请求触发与响应断言等关键实践。
在 Spring Boot 应用中,通过 Filter 实现轻量级请求限流(如按客户端 IP 限制每秒请求数)是一种常见且高效的做法。但其单元测试常因依赖外部组件(如 LoadingCache、HttpServletRequest)而难以覆盖核心逻辑。直接 mock 被测类内部字段(如 requestCountsPerIpAddress)不仅违反测试原则,更因 Caffeine 缓存的线程安全与过期机制导致行为不可控——真正的可测性来源于“构造真实对象 + 模拟协作依赖”,而非篡改被测实例状态。
以下是一个结构清晰、生产可用的测试方案:
✅ 正确做法:集成式单元测试(非纯 mock)
首先,确保测试环境能正确加载配置和 Bean:
# src/test/resources/application.yml
chatGPT:
max:
request:
per:
second: 10然后编写 JUnit 5 测试类,使用 @SpringBootTest(轻量启动)或 @ContextConfiguration 手动注册 Filter Bean。推荐使用 @WebMvcTest 配合 @Import 显式引入 Filter,避免全应用上下文启动:
@WebMvcTest
@Import(RequestFilter.class)
class RequestFilterTest {
@Autowired
private RequestFilter filter;
@Test
void shouldRejectRequestWhenExceedingMaxPerSecond() throws Exception {
// 模拟 Servlet 请求/响应/链
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRemoteAddr("192.168.1.100");
request.addHeader("X-Forwarded-For", "203.0.113.42,198.51.100.1");
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain filterChain = new MockFilterChain(); // Spring Test 提供
// 触发 11 次请求(超过阈值 10)
for (int i = 0; i < 11; i++) {
filter.doFilter(request, response, filterChain);
if (i == 10) { // 第 11 次应被拒绝
assertThat(response.getStatus()).isEqualTo(HttpStatus.TOO_MANY_REQUESTS.value());
assertThat(response.getContentAsString()).isEqualTo("Too many requests");
} else {
assertThat(response.getStatus()).isNotEqualTo(HttpStatus.TOO_MANY_REQUESTS.value());
}
// 重置响应状态,避免累积影响
response.reset();
}
}
}⚠️ 关键说明:
- 不 mock requestCountsPerIpAddress:Caffeine 缓存需保持真实行为(自动过期、原子计数),mock 将破坏限流逻辑本质;
- 使用 MockHttpServletRequest 替代 @Mock HttpServletRequest:它完整支持 getHeader()、getRemoteAddr() 等方法,且可复用;
- 每次调用后调用 response.reset():确保后续请求不受前次响应状态干扰;
- MockFilterChain 不执行后续过滤器/处理器:符合单元测试隔离原则,我们只关注本 Filter 行为。
? 补充:验证 getClientIP() 的健壮性
@Test
void shouldExtractClientIpFromXForwardedForWhenPresent() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("X-Forwarded-For", "10.0.0.1, 192.168.0.2, 203.0.113.5");
String ip = filter.getClientIP(request);
assertThat(ip).isEqualTo("10.0.0.1"); // 取第一个非空项
}
@Test
void shouldFallbackToRemoteAddrWhenXForwardedForAbsent() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRemoteAddr("172.16.0.10");
String ip = filter.getClientIP(request);
assertThat(ip).isEqualTo("172.16.0.10");
}? 注意事项与最佳实践
- ❌ 避免 @MockBean RequestFilter:这会替换 Spring 容器中的真实 Bean,导致缓存初始化失效;
- ✅ 使用 @TestConfiguration 自定义测试专用 Cache(如缩短过期时间为 10ms),加速测试执行;
- ? 若部署在反向代理后(如 Nginx),务必在代理配置中设置 X-Forwarded-For,否则 getRemoteAddr() 返回的是代理 IP;
- ? 生产建议补充集成测试:结合 @AutoConfigureTestDatabase 和真实 HTTP 客户端(如 TestRestTemplate)验证端到端限流效果;
- ? 安全提示:IP 限流易受伪造 X-Forwarded-For 攻击,生产环境应结合 X-Real-IP 或 TLS 客户端证书增强可信度。
通过以上方式,你不仅能 100% 覆盖限流核心逻辑,还能确保测试结果与运行时行为严格一致——这才是高质量单元测试的核心价值。

















