Java 单元测试怎么对使用了缓存的业务逻辑进行测试

大明小哥_6965

大明小哥_6965

2026-10-28

749人浏览

原创

Java单元测试应隔离缓存副作用,专注验证业务逻辑:一、接口封装缓存时用Mockito模拟CacheService;二、注解缓存需抽离为可注入策略并Mock;三、静态缓存须重构为实例字段或Spring Bean管理。

java 单元测试怎么对使用了缓存的业务逻辑进行测试

Java单元测试对使用了缓存的业务逻辑进行测试,关键在于隔离缓存副作用、验证逻辑正确性、不依赖真实缓存行为。缓存本身不是被测逻辑的重点,而是外部依赖——它该被模拟(Mock)或替换为可控实现,而不是让测试跑在 Redis 或 Caffeine 上。

下面从实操角度分三类场景说明怎么做:

一、缓存调用被封装在独立接口中(推荐设计)

比如业务 Service 通过 CacheService 接口读写缓存,而具体实现是 RedisCacheServiceImpl 或 LocalCacheServiceImpl:

public class OrderService {
    private final CacheService cacheService;
    public OrderService(CacheService cacheService) {
        this.cacheService = cacheService;
    }
    public Order getOrder(Long orderId) {
        String key = "order:" + orderId;
        Order cached = cacheService.get(key, Order.class);
        if (cached != null) return cached;
        Order dbOrder = orderDao.findById(orderId);
        cacheService.set(key, dbOrder, 10, MINUTES);
        return dbOrder;
    }
}

✅ 测试做法:

  • 用 Mockito Mock CacheService,控制它的返回值和调用验证
  • 专注验证:缓存命中时是否直接返回、未命中时是否查库并回填、是否设置正确过期时间
@Test
void shouldReturnFromCacheIfPresent() {
    // 给 mock 的 cache 返回一个 Order
    when(cacheService.get("order:123", Order.class)).thenReturn(new Order(123L, "PAID"));

    Order result = service.getOrder(123L);

    assertThat(result.getStatus()).isEqualTo("PAID");
    verify(cacheService, never()).set(any(), any(), anyLong(), any());
}

@Test
void shouldLoadFromDbAndCacheIfAbsent() {
    when(cacheService.get("order:456", Order.class)).thenReturn(null);
    when(orderDao.findById(456L)).thenReturn(new Order(456L, "SHIPPED"));

    Order result = service.getOrder(456L);

    assertThat(result.getStatus()).isEqualTo("SHIPPED");
    verify(cacheService).set(eq("order:456"), any(Order.class), eq(10L), eq(MINUTES));
}

二、缓存注解直接写在方法上(如 @CreateCache / @Cached)

常见于 JetCache、Caffeine、Spring Cache 场景。例如:

@CreateCache(name = "userCache", expire = 60)
private Cache<long user> userCache;

public User getUser(Long id) {
    return userCache.get(id, () -> userDao.findById(id));
}</long>

⚠️ 单元测试不能直接测这个 userCache 字段(它是运行时注入的代理对象),否则会触发真实缓存行为。

✅ 正确做法:

java-api-doc-generator
java-api-doc-generator

分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。

下载
  • 不要测试缓存注解本身(那是框架的事),只测 userDao.findById(id) 是否被正确调用
  • 把 userCache 声明为 protected 或包级访问,测试中用 ReflectionTestUtils.setField() 注入一个 MockCache 实例
  • 或更干净的方式:将缓存逻辑抽成可注入策略,如 CacheLoader,测试时传入 id -> mockUser

示例(JetCache 风格):

// 改造成可注入
public class UserService {
    private final Cache<long user> userCache;
    private final Function<long user> loader;

    public UserService(Cache<long user> cache, Function<long user> loader) {
        this.userCache = cache;
        this.loader = loader;
    }

    public User getUser(Long id) {
        return userCache.get(id, () -> loader.apply(id));
    }
}</long></long></long></long>

测试时传入 mock(Cache.class) 和 id -> new User(id, "test") 即可完全控制。

三、静态缓存或单例缓存(高风险,需重构)

比如:

public class ConfigManager {
    private static final Map<string string> cache = new ConcurrentHashMap();
    public static String get(String key) { ... }
}</string>

❌ 单元测试中直接调用 ConfigManager.get() 会导致状态污染(多个测试共享同一 map)
✅ 解法不是清空 map,而是:

  • 将 cache 变为实例字段,通过构造器注入(如 new ConfigManager(new ConcurrentHashMap()))
  • 或改用 ThreadLocal>,每个测试线程独享副本
  • 或彻底移除静态缓存,改用 Spring Bean 管理生命周期,测试时用 @MockBean 替换
@SpringBootTest
class ConfigManagerTest {
    @MockBean private ConfigRepository configRepo;

    @Test
    void shouldLoadFromRepoWhenNotInCache() {
        when(configRepo.findByKey("timeout")).thenReturn("3000");
        assertThat(configManager.get("timeout")).isEqualTo("3000");
        // 第二次调用应走缓存(若已实现)
        assertThat(configManager.get("timeout")).isEqualTo("3000");
        verify(configRepo, times(1)).findByKey("timeout");
    }
}

注意:这种测试已属于集成/组件测试范畴,需启动 Spring 上下文;纯单元测试仍建议走构造注入 + Mock 方式。

不复杂但容易忽略:缓存只是加速手段,业务逻辑的正确性不依赖它是否存在;测试要守住这个边界,才能又快又稳。

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

11397

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

8502

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

7332

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

1224

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

988

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1456

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2989

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

20251

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1315

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习