Spring Boot中Filter请求拦截的单元测试问题求助
问题:Spring Boot请求限流Filter的单元测试困境
场景说明
我用Spring Boot的Filter实现了请求次数限制,但编写单元测试时遇到了麻烦。核心需求是mock requestCountsPerIpAddress.get(clientIpAddress)的调用,验证当请求数超过阈值时返回限流结果,但尝试了@Mock、mock Filter类等多种方式都没成功。
实现的Filter代码
@Component public class RequestFilter implements Filter { @Value("${chatGPT.max.request.per.second}") private int MAX_REQUESTS_PER_SECOND; private final LoadingCache<String, Integer> requestCountsPerIpAddress; public RequestFilter() { super(); requestCountsPerIpAddress = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.SECONDS) .build(key -> 0); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { var httpServletResponse = (HttpServletResponse) servletResponse; var clientIpAddress = getClientIP((HttpServletRequest) servletRequest); if(isMaximumRequestsPerSecondExceeded(clientIpAddress)) { httpServletResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); httpServletResponse.getWriter().write("Too many requests"); return; } filterChain.doFilter(servletRequest, servletResponse); } // RequestFilterMaxRequestExceededTest public boolean isMaximumRequestsPerSecondExceeded(String clientIpAddress){ boolean max_request_exceeded = true; var requests = requestCountsPerIpAddress.get(clientIpAddress); if(Objects.isNull(requests)) { // if there are no requests tracked yet, requests to be initialized requests = 0; } if(requests > MAX_REQUESTS_PER_SECOND) { return max_request_exceeded; } requests++; requestCountsPerIpAddress.put(clientIpAddress, requests); return !max_request_exceeded; } public String getClientIP(HttpServletRequest request) { String xfHeader = request.getHeader("X-Forwarded-For"); if (xfHeader == null){ return request.getRemoteAddr(); } return xfHeader.split(",")[0]; } }
测试目标
我希望验证:当请求数i>9时(假设MAX_REQUESTS_PER_SECOND设为9),调用isMaximumRequestsPerSecondExceeded会返回true。我的测试代码如下:
@DisplayName("---") @Test void requestFilterMaxRequestExceededTest() { for (int i=1; i < 12; i++) { when(filter.requestCountsPerIpAddress.get(clientIpAddress)).thenReturn(i); if (i>9) { assertThat(filter.isMaximumRequestsPerSecondExceeded("testIp")) .isTrue(); } } }
解决思路
1. 重构Filter,通过构造注入解耦缓存
原来的Filter在构造方法里直接实例化Caffeine缓存,导致无法替换为mock对象。修改成构造注入的方式,同时保留无参构造供Spring容器使用:
@Component public class RequestFilter implements Filter { @Value("${chatGPT.max.request.per.second}") private int MAX_REQUESTS_PER_SECOND; private final LoadingCache<String, Integer> requestCountsPerIpAddress; // 新增带参数的构造方法,用于测试注入mock缓存 public RequestFilter(LoadingCache<String, Integer> requestCountsPerIpAddress) { this.requestCountsPerIpAddress = requestCountsPerIpAddress; } // 保留无参构造,供Spring自动装配 public RequestFilter() { this.requestCountsPerIpAddress = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.SECONDS) .build(key -> 0); } // 其余方法保持不变... }
2. 编写单元测试,mock LoadingCache
使用Mockito创建缓存的mock实例,注入到Filter中,同时手动设置阈值参数:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.DisplayName; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.junit.jupiter.api.extension.ExtendWith; import com.github.benmanes.caffeine.cache.LoadingCache; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class RequestFilterTest { @Mock private LoadingCache<String, Integer> mockRequestCache; @Test @DisplayName("请求数超过阈值时触发限流") void requestFilterMaxRequestExceededTest() { // 初始化Filter,传入mock缓存 RequestFilter filter = new RequestFilter(mockRequestCache); // 手动设置限流阈值(因为@Value在单元测试中不会自动注入) filter.MAX_REQUESTS_PER_SECOND = 9; String clientIp = "testIp"; for (int i = 1; i < 12; i++) { // mock缓存的get方法返回当前循环的请求数 when(mockRequestCache.get(clientIp)).thenReturn(i); boolean isExceeded = filter.isMaximumRequestsPerSecondExceeded(clientIp); if (i > 9) { assertThat(isExceeded).isTrue(); } else { assertThat(isExceeded).isFalse(); } } } }
3. 额外提示
- 如果不想修改Filter构造方法,也可以用反射直接设置
requestCountsPerIpAddress私有字段为mock对象,但构造注入是更优雅的解耦方案,利于长期维护。 - 原Filter的限流逻辑有个小细节:当请求数等于阈值时,仍会允许请求并计数+1。如果预期是阈值为9时,第9次请求就触发限流,需要把判断条件从
requests > MAX_REQUESTS_PER_SECOND改成requests >= MAX_REQUESTS_PER_SECOND。
内容的提问来源于stack exchange,提问作者Simon
相关产品推荐
相关产品推荐

