Spring Boot 单元测试:@SpyBean 使用教程

Spring Boot 单元测试:@SpyBean 使用教程

1. 什么是 @SpyBean?

@SpyBean 是 Spring Boot Test 提供的一个注解,用于在单元测试中**部分模拟(Partial Mock)**一个 Spring Bean。它类似于 Mockito 的 @Spy,但专门用于 Spring 容器中的 Bean。

  • @MockBean:完全模拟一个 Bean,所有方法都需要手动定义行为。
  • @SpyBean:基于真实 Bean,只重写(Mock)部分方法,其余方法仍然调用真实逻辑。

2. 何时使用 @SpyBean?

✅ 适用场景:

  • 你只想修改某个 Bean 的某个方法,但其他方法仍然走真实逻辑。
  • 你希望测试依赖某个 Bean 的真实逻辑,但需要控制其中某个方法的返回值。

❌ 不适用场景:

  • 需要完全模拟整个 Bean(用 @MockBean)。
  • Bean 的方法是 final 或 static(Mockito 无法 Spy final 方法)。

3. 基本用法

示例场景

假设有一个 UserService 依赖 UserRepository,我们想测试 UserService,但只重写 UserRepository 的 findById() 方法,其他方法(如 save())仍然走真实逻辑。

java 复制代码
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public String getUserName(Long id) {
        User user = userRepository.findById(id).orElseThrow();
        return user.getName();
    }
}

测试代码

java 复制代码
@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @SpyBean  // 对 UserRepository 进行 Spy,未 Mock 的方法仍然走真实逻辑
    private UserRepository userRepository;

    @Test
    public void testGetUserName() {
        // 准备测试数据
        User mockUser = new User(1L, "Alice");
        userRepository.save(mockUser);  // 真实调用 save()

        // 只 Mock findById(),让它返回自定义数据
        doReturn(Optional.of(new User(1L, "Mocked Alice")))
            .when(userRepository).findById(1L);

        // 调用测试方法
        String userName = userService.getUserName(1L);

        // 验证
        assertEquals("Mocked Alice", userName);

        // 其他方法仍然走真实逻辑
        User savedUser = userRepository.findById(2L).orElse(null);
        assertNull(savedUser);  // 因为没有存 ID=2 的用户
    }
}

4. @SpyBean 的常用 Mock 方式

(1) doReturn().when(spy).method()

java 复制代码
// 让方法返回固定值
doReturn("Mocked Result").when(mySpyBean).someMethod();

// 带参数匹配
doReturn("Hello").when(mySpyBean).greet(anyString());

(2) doAnswer()(自定义逻辑)

java 复制代码
doAnswer(invocation -> {
    String arg = invocation.getArgument(0);
    return "Processed: " + arg;
}).when(mySpyBean).process(anyString());

(3) doThrow()(模拟异常)

java 复制代码
doThrow(new RuntimeException("DB Error"))
    .when(mySpyBean).save(any());

(4) doCallRealMethod()(显式调用真实方法)

java 复制代码
// 默认会走真实方法,但也可以显式声明
doCallRealMethod().when(mySpyBean).someMethod();

5. @SpyBean vs @MockBean

特性 @SpyBean @MockBean
默认行为 未 Mock 的方法调用真实逻辑 未 Mock 的方法返回默认值(null/0/false)
适用场景 只修改部分方法,其他方法仍需真实逻辑 完全模拟 Bean,不依赖真实实现
性能 稍慢(需代理真实对象) 更快(纯 Mock 对象)

6. 常见问题

Q1: @SpyBean 能用于 final 方法吗?

❌ 不能! Mockito 无法 Spy final 方法。如果遇到 final 方法,考虑:

  • 改用 @MockBean 完全模拟。
  • 重构代码,避免 final 方法。

Q2: @SpyBean 和 @Spy 的区别?

  • @Spy 是 Mockito 提供的,用于普通对象。
  • @SpyBean 是 Spring Boot 提供的,用于 Spring Bean。

Q3: 如何验证方法调用次数?

java 复制代码
// 验证 someMethod 被调用 1 次
verify(mySpyBean, times(1)).someMethod();

7. 总结

  • @SpyBean 适合部分 Mock,只修改某些方法,其余方法仍然走真实逻辑。
  • 常用方式 :
    • doReturn().when(spy).method():返回固定值。
    • doAnswer():自定义逻辑。
    • doThrow():模拟异常。
  • 不要用于 final 方法。

🚀 现在你可以在 Spring Boot 测试中灵活使用 @SpyBean 了! 如果有问题,欢迎讨论!

相关推荐
paopaokaka_luck6 小时前
基于springboot+vue的智慧家政管理系统(AI问答、 WebSocket实时沟通、ECharts运营分析、订单规则与服务追踪)
java·javascript·spring boot·数据分析·echarts·mybatis
阿森_dev6 小时前
2027 届毕设选题怎么选:30 个 Java 方向选题的实现难度排序
java·开发语言·spring boot·毕业设计·课程设计
计算机毕业设计杰瑞7 小时前
【计算机毕设精品】基于SpringBoot的一体化教务与学业质量管理系统的设计与实现,附源码_增删改查_java项目_可完美运行部署_网站项目
java·spring boot·课程设计
大侠归来9 小时前
Spring Boot 2.1 → 3.5 迁移推演:从实战出发的完整路线图
java·spring boot·后端
Sweet锦9 小时前
jDCS 开源项目:面向工业现场的 Modbus RTU 数据采集基础框架
java·spring boot·物联网·开源
海马9 小时前
Spring Boot 开发知识整理
java·spring boot·后端
谢亮_vipxieliang9 小时前
Spring Boot 3.x 从零开始——环境搭建与第一个 REST 项目
java·spring boot·后端
FYKJ_201010 小时前
django个性化新闻推荐35173-计算机课程设计、毕业设计
java·spring boot·后端·python·架构·django·课程设计
今年下半年10 小时前
【消息队列】Spring Boot 集成 Kafka 消息队列:确保消费完成(手动提交偏移量+幂等判断+失败重试机制)
spring boot·kafka·幂等·失败重试
计算机毕设定制辅导-无忧学长10 小时前
《校园食堂在线订餐小程序的设计与实现》
java·vue.js·spring boot·微信小程序·uniapp·校园食堂在线订餐小程序