如何通过JUnit+Spring集成测试验证@Cacheable方法的Redis超时异常处理
如何测试自定义的CacheErrorHandler处理Redis异常
我来帮你搞定这个单元测试的难题!要验证你的GlobalCacheErrorHandler是否能正确处理Redis超时这类异常,关键是要模拟缓存操作抛出异常的场景,不用真的搭建Redis服务。下面是两种可行的方案,从简单的单元测试到集成测试都有:
方案1:直接单元测试ErrorHandler方法(快速验证)
如果你只是想验证GlobalCacheErrorHandler的方法逻辑(比如日志是否正确输出),可以直接实例化它,手动调用对应的方法,然后验证日志行为:
import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.Cache; import org.springframework.data.redis.RedisTimeoutException; import static org.assertj.core.api.AssertionsForClassTypes.then; class GlobalCacheErrorHandlerTest { private final GlobalCacheErrorHandler errorHandler = new GlobalCacheErrorHandler(); private final Logger logger = LoggerFactory.getLogger(GlobalCacheErrorHandler.class); @Test void handleCacheGetError_shouldLogCorrectMessage() { // 准备模拟数据 RedisTimeoutException timeoutException = new RedisTimeoutException("Test timeout"); Cache mockCache = () -> "EMPLOYEE_CACHE"; // 简单模拟Cache对象 // 调用目标方法 errorHandler.handleCacheGetError(timeoutException, mockCache, "EId_1"); // 验证日志是否正确输出 then(logger).should().error("Could not get the value from cache"); } }
这种方式简单直接,适合快速验证ErrorHandler的基础逻辑,但无法模拟@Cacheable注解触发缓存操作的完整流程。
方案2:集成测试(模拟@Cacheable触发缓存异常)
如果你想验证当@Cacheable方法执行时,Redis异常是否会被正确传递给ErrorHandler,需要结合Spring测试框架和Mockito来模拟整个缓存流程:
步骤1:配置测试类
我们需要加载必要的Spring配置,同时替换掉真实的RedisCacheManager为模拟对象:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.cache.Cache; import org.springframework.data.redis.cache.RedisCache; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.RedisTimeoutException; import static org.mockito.Mockito.*; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(MockitoExtension.class) @SpringBootTest(classes = {ApplicationCacheConfiguration.class}) class EmployeeRepositoryCacheErrorTest { @MockBean private RedisCacheManager redisCacheManager; @Autowired private EmployeeRepository employeeRepository; @Autowired private GlobalCacheErrorHandler cacheErrorHandler; @Captor private ArgumentCaptor<RuntimeException> exceptionCaptor; @Test void whenCacheGetThrowsTimeout_thenErrorHandlerHandlesIt() { // 1. 模拟RedisCache对象,让它的get方法抛出超时异常 RedisCache mockCache = mock(RedisCache.class); when(mockCache.get(anyObject())) .thenThrow(new RedisTimeoutException("Redis connection timed out")); // 2. 让CacheManager返回我们模拟的缓存实例 when(redisCacheManager.getCache("EMPLOYEE_CACHE")) .thenReturn(mockCache); // 3. Spy包装ErrorHandler,方便验证方法调用 GlobalCacheErrorHandler spyErrorHandler = spy(cacheErrorHandler); // 4. 调用被@Cacheable注解的方法 employeeRepository.findByEmplyeeId(1L); // 5. 验证ErrorHandler的方法是否被正确调用 verify(spyErrorHandler).handleCacheGetError( exceptionCaptor.capture(), any(Cache.class), eq("EId_1") ); // 6. 确认捕获的异常是RedisTimeoutException assertThat(exceptionCaptor.getValue()) .isInstanceOf(RedisTimeoutException.class) .hasMessageContaining("Redis connection timed out"); } }
关键细节说明:
- @MockBean:替换Spring容器中的真实
RedisCacheManager,这样我们可以完全控制缓存的行为,让它抛出我们需要的异常。 - Spy包装ErrorHandler:因为
GlobalCacheErrorHandler是Spring创建的Bean,我们需要用Spy来包装它,才能验证它的方法是否被调用。 - 模拟缓存操作:让
RedisCache的get方法抛出RedisTimeoutException,这样当@Cacheable方法尝试从缓存获取数据时,就会触发异常,进而调用ErrorHandler。 - 验证逻辑:用
ArgumentCaptor捕获异常,确认是我们期望的Redis超时异常,同时验证handleCacheGetError方法被正确调用。
额外注意事项
- 如果你的
EmployeeRepository是Spring Data的接口(比如继承JpaRepository),记得用@MockBean替换它的实现,避免测试时调用真实数据库:@MockBean private EmployeeRepository employeeRepository; // 模拟数据库查询返回结果 when(employeeRepository.findByEmplyeeId(anyLong())) .thenReturn(List.of(new Employee())); - 如果你想验证日志输出,可以使用AssertJ的日志断言工具,具体依赖和用法可以参考方案1中的示例。
这样就能完整测试你的GlobalCacheErrorHandler在Redis异常场景下的表现了!
内容的提问来源于stack exchange,提问作者Bhargav Choksi
相关产品推荐
相关产品推荐

