SpringBoot测试中如何为每个测试用例重新定义@SpyBean?
问题根因
两个异常现象的本质原因很明确:
- 跨用例脏数据:Spring Boot测试的应用上下文默认会根据配置缓存复用,
@SpyBean生成的代理实例是容器内的单例,所有测试用例共享这一个对象。用例中直接修改它的属性后如果没有还原,后续用例自然会读到修改后的值。 - 手动new实例无效:
@BeforeEach里写myProperties = new MyProperties(),只是把测试类自己的成员变量指向了新对象,Spring容器里实际注入给被测组件的还是最开始生成的那个Spy实例,被测逻辑根本拿不到你new的新对象,自然不会有任何效果。
可行解决方案
方案1:用Mockito重置Spy实例(最推荐,执行效率最高)
不需要手动new新对象,在@BeforeEach中调用Mockito的reset方法清空Spy实例的历史状态,有需要可以统一设置属性初始值:
import org.mockito.Mockito; import org.springframework.test.util.ReflectionTestUtils; @BeforeEach void setUp() { // 重置spy对象,清除之前所有测试设置的存根、属性改动 Mockito.reset(myProperties); // 如需给配置设置全局默认值,直接通过反射修改现有spy实例的属性即可 // 示例:给enabled属性设置默认值true ReflectionTestUtils.setField(myProperties, "enabled", true); }
这种方式不会破坏Spring容器的Bean引用关系,重置后单个用例内修改属性、打方法存根都会正常生效,也不会出现跨用例脏数据,执行速度和普通测试几乎没有差异。
方案2:用方法存根替代直接修改属性
如果被测组件是通过getter方法读取配置值,直接给Spy对象打存根返回目标值即可,不需要反射修改私有属性:
@Test void testWhenConfigDisabled() { // 直接指定getEnabled()返回false,无需关心属性的访问权限 Mockito.when(myProperties.getEnabled()).thenReturn(false); // 执行被测业务逻辑... }
存根逻辑配合Mockito.reset()使用,完全不会出现跨用例数据污染,代码可读性也更高。
方案3:标记上下文为脏数据(非特殊场景不推荐)
如果测试中对Spring上下文的改动较多,无法通过重置单个Bean恢复,可以在测试类上添加注解,让每个用例执行完后重新加载整个上下文:
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
这种方式每次都会重建Spring容器,测试执行速度会大幅降低,仅在上下文改动无法单独回滚时使用。
注意事项
不要手动给@MockBean、@SpyBean标注的测试类字段重新赋值:这类字段的引用是在测试上下文启动阶段,就和容器内Bean、被测组件的依赖绑定完成的,手动修改测试类字段的指向,不会同步更新容器内的依赖关系,所有修改都不会作用到被测逻辑上。
内容的提问来源于stack exchange,提问作者Greg
相关产品推荐
相关产品推荐

