You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JUnit单元测试中正确Mock Guava LoadingCache的方法咨询

问题根因

你的测试不生效核心是两个问题:

  • 逻辑冲突:你同时用@Mock声明tenantCache模拟实例、手动构建真实LoadingCache实例覆盖测试类变量,两套逻辑完全互斥。
  • 依赖未注入:不管你准备的是mock缓存还是真实缓存,都没有替换掉被测类内部初始化的私有tenantCache字段,被测方法运行时调用的始终是类自带的原始缓存实例,和你测试代码里构造的缓存没有任何关系。
可行实现方案

二选一即可,优先选方案1,单元测试不需要依赖真实缓存组件,运行速度更快、边界场景更易模拟。

方案1:纯Mock实现(推荐)

不需要构建真实Guava缓存,直接Mock缓存的行为即可,步骤如下:

  1. 用@InjectMocks标记被测类实例,Mockito会自动把@Mock标注的依赖注入到被测类对应字段中
  2. 按需模拟tenantCache.get()的返回值/抛出异常,不需要手动put缓存数据
  3. 覆盖正常返回、空入参、租户不存在三类场景即可

参考测试代码:

// 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 07:36:32