JUnit单元测试中正确Mock Guava LoadingCache的方法咨询
问题根因
你的测试不生效核心是两个问题:
- 逻辑冲突:你同时用
@Mock声明tenantCache模拟实例、手动构建真实LoadingCache实例覆盖测试类变量,两套逻辑完全互斥。 - 依赖未注入:不管你准备的是mock缓存还是真实缓存,都没有替换掉被测类内部初始化的私有
tenantCache字段,被测方法运行时调用的始终是类自带的原始缓存实例,和你测试代码里构造的缓存没有任何关系。
可行实现方案
二选一即可,优先选方案1,单元测试不需要依赖真实缓存组件,运行速度更快、边界场景更易模拟。
方案1:纯Mock实现(推荐)
不需要构建真实Guava缓存,直接Mock缓存的行为即可,步骤如下:
- 用
@InjectMocks标记被测类实例,Mockito会自动把@Mock标注的依赖注入到被测类对应字段中 - 按需模拟
tenantCache.get()的返回值/抛出异常,不需要手动put缓存数据 - 覆盖正常返回、空入参、租户不存在三类场景即可
参考测试代码:
// JUnit5用@ExtendWith,JUnit4替换为@RunWith(MockitoJUnitRunner.class) @ExtendWith(MockitoExtension.class) public class TenantServiceTest { // 被测类,自动注入所有@Mock标记的依赖 @InjectMocks private TenantService tenantService; @Mock private LoadingCache<String, Tenant> tenantCache; @Test void getTenantName_shouldReturnCorrectName_whenTenantExists() throws ExecutionException { // 准备测试数据 String validTenantId = "test"; Tenant mockTenant = new Tenant(); mockTenant.setTenantname("测试企业"); // 模拟缓存命中返回 when(tenantCache.get(validTenantId)).thenReturn(mockTenant); // 执行被测方法 String result = tenantService.getTenantName(validTenantId); // 断言结果 assertEquals("测试企业", result); } @Test void getTenantName_shouldThrowException_whenTenantIdBlank() { // 空字符串、null入参场景 assertThrows(TenantNotFoundException.class, () -> tenantService.getTenantName("")); assertThrows(TenantNotFoundException.class, () -> tenantService.getTenantName(null)); } @Test void getTenantName_shouldThrowException_whenTenantNotExist() throws ExecutionException { String invalidTenantId = "not_exist"; // 模拟缓存查询时抛出异常,对应getTenant方法里的异常捕获逻辑 when(tenantCache.get(invalidTenantId)).thenThrow(new ExecutionException(new RuntimeException("not found"))); assertThrows(TenantNotFoundException.class, () -> tenantService.getTenantName(invalidTenantId)); } }
方案2:使用真实LoadingCache实例(需验证缓存逻辑时用)
如果你需要测试缓存本身的命中、过期、load逻辑,就不要给tenantCache加@Mock注解,手动构建真实缓存实例后通过反射注入到被测类即可:
@ExtendWith(MockitoExtension.class) public class TenantServiceTest { @InjectMocks private TenantService tenantService; // 只mock外部依赖的tenantClient,缓存用真实实例 @Mock private TenantClient tenantClient; private LoadingCache<String, Tenant> tenantCache; @BeforeEach void setUp() { // 构建和业务代码配置一致的真实缓存 tenantCache = CacheBuilder.newBuilder().maximumSize(1000) .expireAfterAccess(24, TimeUnit.HOURS).build(new CacheLoader<String, Tenant>() { @Override public Tenant load(String tenantId) { return tenantClient.getTenant(tenantId); } }); // 把真实缓存实例注入到被测类的私有tenantCache字段 ReflectionTestUtils.setField(tenantService, "tenantCache", tenantCache); } @Test void getTenantName_shouldReturnFromCache_whenCacheHit() { String testTenantId = "test"; Tenant cacheTenant = new Tenant(); cacheTenant.setTenantname("缓存内租户"); // 提前往缓存放数据,模拟命中场景 tenantCache.put(testTenantId, cacheTenant); String result = tenantService.getTenantName(testTenantId); assertEquals("缓存内租户", result); // 验证缓存命中时不会调用外部client verify(tenantClient, never()).getTenant(any()); } @Test void getTenantName_shouldLoadFromClient_whenCacheMiss() { String missTenantId = "miss_id"; Tenant clientTenant = new Tenant(); clientTenant.setTenantname("客户端查询租户"); // 模拟客户端返回 when(tenantClient.getTenant(missTenantId)).thenReturn(clientTenant); String result = tenantService.getTenantName(missTenantId); assertEquals("客户端查询租户", result); // 验证缓存未命中时触发了load逻辑调用客户端 verify(tenantClient, times(1)).getTenant(missTenantId); } }
注意:
ReflectionTestUtils是spring-test提供的工具类,如果项目没有引入Spring,直接写JDK原生反射代码修改私有字段值即可,逻辑完全一致。
内容的提问来源于stack exchange,提问作者Harsh
相关产品推荐
相关产品推荐

