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

Spring环境下如何Mock同一类中method1调用的method2?

问题:Spring Boot中Mock同一类内方法调用的测试问题

我有一个Spring Boot应用,架构为:@Controller > @Service > @Component。

我的Component代码:

@Component
public class myComponent {
    @Autowired
    private ObjectMapper mapper; //com.fasterxml.jackson.databind.ObjectMapper
    @Autowired
    private Component2 component2; // some 3rd part REST service
    @Autowired
    private Component3 component3; //database access

    @Transactional(propagation = Propagation.REQUIRED)
    public List<SomePOJO> method1(String otherString) {
        String newOne = myString + otherString; //more logic here, but without components
        return this.method2(newOne);
    }

    public List<SomePojo> method2(String newOne){
        String one = component3.methodX(newOne); //access database with component3
        return component2.methodY(one); //more logic here, including component2 and mapper!
    }
}

测试类基础结构:

我的测试类继承自以下抽象类:

@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public abstract class AbstractRestTest {
    @Autowired
    private WebApplicationContext wac;
    protected Dsl dsl = Dsl.getInstance();

    @Before
    public void globalSetup() {
        RestAssuredMockMvc.webAppContextSetup(wac);
    }
}

测试代码及问题现象:

@MockBean
public myComponent component;

@Before
public void before() throws Exception {
    MockitoAnnotations.initMocks(this);
    List<POJO> someList = new ArrayList<>(); //list populated with some specific POJO's
    // 之前Mock method1时测试成功
    // Mockito.when(component.method1("specificString")).thenReturn(someList);
    
    // 现在改为Mock method2
    Mockito.when(component.method2("specificString2")).thenReturn(someList);
}

@Test
public void recuperaDados() throws Exception {
    // 该请求最终会触发myComponent.method1("specificString"),进而调用method2("specificString2")
    String response1 = given().authentication().preemptive().oauth2("loginInfo")
        .get("myComponent_method1_path/" + "specificString1").asString();
    
    // 直接调用method2,返回预期的序列化结果
    String response2 = component.method2("specificString2");
    
    // 传入未Stub的参数,返回null(符合预期)
    String response3 = component.method2("randomString");
}

现在的问题是:response2和response3都符合预期,但response1是空字符串"",我期望它和response2一致,或者至少返回null。请问如何正确Mock同一类中被其他方法调用的方法并进行测试?


原因分析

你遇到的核心问题是:@MockBean会创建一个完全的Mock对象,而非真实的myComponent实例。

当你通过接口调用触发method1时,执行的是Mockito生成的Mock版method1——它不会执行你写的真实业务逻辑(也就是不会主动去调用method2),而是返回Mockito的默认值(对于List类型是null,序列化后可能变成空字符串)。而直接调用method2时,因为你已经Stub了这个方法,所以能返回预期结果。

解决方案

针对这个场景,有两种常用的解决思路:

方案1:让Mock对象的method1执行真实逻辑,同时Stubmethod2

通过Mockito的doCallRealMethod()方法,指定Mock对象的method1执行真实代码,而method2使用你Stub的逻辑。修改测试的@Before方法:

@Before
public void before() throws Exception {
    MockitoAnnotations.initMocks(this);
    List<POJO> someList = new ArrayList<>();
    // 填充测试数据...

    // 让method1执行真实逻辑
    Mockito.doCallRealMethod().when(component).method1(Mockito.anyString());
    // Stub method2的返回值
    Mockito.when(component.method2("specificString2")).thenReturn(someList);
}

这样,当接口调用触发method1时,会执行你写的真实method1代码,进而调用被Stub的method2,最终返回你预期的someList,序列化后和response2一致。

方案2:MockmyComponent的依赖而非整个类(更推荐)

这种方式更符合单元测试的原则:只Mock外部依赖(component2、component3、mapper),让myComponent保持真实实例,这样method1会自然调用真实的method2,而method2中依赖的外部服务/数据库操作被Mock。

修改测试类:

// 移除@MockBean myComponent,改为Mock它的依赖
@MockBean
private Component2 component2;
@MockBean
private Component3 component3;
@MockBean
private ObjectMapper mapper;

// 注入真实的myComponent实例
@Autowired
private myComponent component;

@Before
public void before() throws Exception {
    MockitoAnnotations.initMocks(this);
    List<POJO> someList = new ArrayList<>();
    // 填充测试数据...

    // Stub component3的methodX返回值,让method2能走到下一步
    Mockito.when(component3.methodX("specificString")).thenReturn("mockResultFromDB");
    // Stub component2的methodY返回预期的someList
    Mockito.when(component2.methodY("mockResultFromDB")).thenReturn(someList);
    // 如果需要,也可以Stub mapper的序列化逻辑
}

这种方式的优势在于:

  • 测试的是myComponent的真实业务逻辑,而非Mock对象的行为
  • 更贴近实际运行场景,能发现method1和method2之间的逻辑问题
  • 避免了Mock同一类内方法的尴尬场景
注意事项
  • 如果你使用方案1,要注意method1中的@Transactional注解——因为Mock对象不会被Spring的事务管理器代理,所以事务注解不会生效。如果你的业务逻辑依赖事务,方案2会更合适。
  • 确保测试数据的参数匹配正确:比如method1中生成的newOne确实是"specificString2",否则Stub的method2不会触发。

内容的提问来源于stack exchange,提问作者gabriel119435

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:08:08