Mockito与循环依赖:如何调用类的实际实现?
问题场景
循环依赖的Spring组件
有两个相互引用形成循环依赖的Spring组件:
@AllArgsConstructor @Component public class A { private B b; public String doSomething() { return "Result: " + b.doSomethingElse(); } } @Component // 注:原代码存在拼写错误,已修正为Component public class B { private A a; @Autowired public B(@Lazy final A a) { this.a = a; } public String doSomethingElse() { return a.toString(); } }
测试需求与报错
需要测试类A,确保调用B的实际实现,因此使用Mockito的@Spy编写测试代码:
@RunWith(MockitoJUnitRunner.class) public class ATest { @InjectMocks private A a; @Spy private B b; @Test public void testDoSomething() { final String result = a.doSomething(); assertNotNull(result); } }
但运行时始终抛出错误:
Unable to initialize @Spy annotated field 'b'. Please ensure that the type 'B' has a no-arg constructor. org.mockito.exceptions.base.MockitoException: Unable to initialize @Spy annotated field 'b'. Please ensure that the type 'B' has a no-arg constructor.
使用@Mock只能模拟返回值,无法满足调用B实际实现的需求,寻求解决办法。
解决办法
方案1:手动创建Spy实例并注入
Mockito默认通过无参构造器创建Spy对象,而B只有带参构造器,因此可以手动构建Spy实例并完成依赖注入,同时处理循环调用问题:
@RunWith(MockitoJUnitRunner.class) public class ATest { private A a; @Spy private B b; @Before public void setUp() throws NoSuchFieldException, IllegalAccessException { // 手动创建A实例,传入B的Spy对象 a = new A(b); // Mock A的toString方法,避免B调用时触发循环调用栈溢出 A mockA = mock(A.class); when(mockA.toString()).thenReturn("MockedA"); // 通过反射给B的Spy注入Mock的A实例 Field aField = B.class.getDeclaredField("a"); aField.setAccessible(true); aField.set(b, mockA); } @Test public void testDoSomething() { final String result = a.doSomething(); assertNotNull(result); // 验证B的实际方法被调用 verify(b).doSomethingElse(); } }
方案2:给B添加无参构造器+Setter注入
修改B类,添加无参构造器并改用Setter注入A,让Mockito可以自动创建Spy:
@Component public class B { private A a; // 添加无参构造器 public B() {} @Autowired public void setA(@Lazy A a) { this.a = a; } public String doSomethingElse() { return a.toString(); } }
测试类调整为:
@RunWith(MockitoJUnitRunner.class) public class ATest { @InjectMocks @Spy private A a; @Spy private B b; @Before public void setUp() { // Mock A的toString方法避免循环调用 when(a.toString()).thenReturn("MockedA"); } @Test public void testDoSomething() { final String result = a.doSomething(); assertNotNull(result); verify(b).doSomethingElse(); } }
方案3:使用Spring Test加载真实上下文
借助Spring Test的上下文加载真实Bean,Spring会自动处理@Lazy的循环依赖,同时用@SpyBean包装真实的BBean,既保留实际实现,又支持按需Mock:
@RunWith(SpringRunner.class) @SpringBootTest(classes = {A.class, B.class}) public class ATest { @Autowired private A a; @SpyBean private B b; @Test public void testDoSomething() { final String result = a.doSomething(); assertNotNull(result); verify(b).doSomethingElse(); } }
内容的提问来源于stack exchange,提问作者j3d
相关产品推荐
相关产品推荐

