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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 04:48:32