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

Spring Boot服务层JUnit测试:Mocked Bean未执行问题排查

问题描述

我正在使用Spring Boot REST API,想要测试服务层。我的服务类通过@Autowired字段注入MyRepository和ResponseMapper,没有用构造器注入(我偏好这种方式简化代码)。在JUnit测试中,我用MockitoJUnitRunner,通过@InjectMocks注入MyService,用@Mock注解了MyRepository和ResponseMapper。因为希望ResponseMapper的mapToResponseData方法实际执行而非被Mock,我尝试用ReflectionTestUtils.setField给MyService注入responseMapper,但调试发现这个方法根本没执行,导致断言失败。我知道可以Mock这个方法,但希望直接执行它来避免单独为Mapper写测试类。请问我哪里操作错了?这种注入Bean的方式是否正确?服务类必须用构造器注入Bean吗?


服务类代码

@Component
public class MyService {

    @Autowired
    private MyRepository myRepository;

    @Autowired
    private ResponseMapper responseMapper;
    
    public List<MyObj> getList(String param) throws MyException {
        log.info("Getting details");
        Optional<List<MyObj>> list = myRepository.findByParam(param);
        List<MyObj> data = responseMapper.mapToResponseData(list);
        return data;
    }
}

测试类代码

@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {

    @InjectMocks
    private MyService myService;

    @Mock
    private MyRepository myRepository;
    
    @Mock
    private ResponseMapper responseMapper;
    
    @Before
    public void setUp() {
        ReflectionTestUtils.setField(myService, "responseMapper", responseMapper);
    }
    
    @Test
    public void getListTest() throws Exception {
        when(myRepository.findByParam(anyString()))
                .thenReturn(Optional.of(getSampleList()));
        List<MyObj> list = myService.getList("param");
        assertTrue(list.size() >0);
    }
}

解决方案与解释

1. 你的操作错误在哪里?

核心问题出在ResponseMapper的实例上:你用@Mock注解了它,Mockito已经为它创建了一个Mock对象,之后用ReflectionTestUtils.setField注入的还是这个Mock对象,而非真实实例。Mock对象的所有方法默认返回空值(比如List返回空列表),所以mapToResponseData不会执行真实逻辑,自然导致断言失败。

要让ResponseMapper执行真实方法,你需要:

  • 去掉@Mock注解,直接创建它的真实实例(比如new ResponseMapper(),前提是它没有其他依赖);
  • 如果ResponseMapper本身有Spring依赖,可以改用Spring Boot测试框架(@SpringBootTest+@MockBean)来管理Bean,但测试会相对重一些。

修改后的测试类示例:

@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {

    @InjectMocks
    private MyService myService;

    @Mock
    private MyRepository myRepository;
    
    // 直接创建真实实例,而非Mock
    private ResponseMapper responseMapper = new ResponseMapper();
    
    @Before
    public void setUp() {
        ReflectionTestUtils.setField(myService, "responseMapper", responseMapper);
    }
    
    @Test
    public void getListTest() throws Exception {
        when(myRepository.findByParam(anyString()))
                .thenReturn(Optional.of(getSampleList()));
        List<MyObj> list = myService.getList("param");
        assertTrue(list.size() > 0);
    }
    
    // 补充测试数据方法
    private List<MyObj> getSampleList() {
        return Arrays.asList(new MyObj(), new MyObj());
    }
}

另外,其实@InjectMocks会自动注入字段,如果你已经声明了真实的responseMapper,可以不用ReflectionTestUtils,直接让Mockito自动注入:

@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {

    @InjectMocks
    private MyService myService;

    @Mock
    private MyRepository myRepository;
    
    // 直接声明真实实例,@InjectMocks会自动注入到MyService中
    private ResponseMapper responseMapper = new ResponseMapper();
    
    @Test
    public void getListTest() throws Exception {
        when(myRepository.findByParam(anyString()))
                .thenReturn(Optional.of(getSampleList()));
        List<MyObj> list = myService.getList("param");
        assertTrue(list.size() > 0);
    }
}

2. 这种注入Bean的方式是否正确?

字段注入(@Autowired标注在字段上)是Spring支持的注入方式,但存在几个明显缺点:

  • 无法保证依赖非空,容易出现NullPointerException;
  • 类的依赖关系不直观,从构造器无法直接看出需要哪些Bean;
  • 测试时需要用反射或@InjectMocks注入依赖,不如构造器注入简洁。

但如果你偏好这种方式简化代码,业务代码中是可以使用的,Spring官方并没有完全禁止,只是更推荐构造器注入。

3. 服务类必须使用构造器注入Bean吗?

不是必须的,但构造器注入是Spring官方推荐的方式,原因如下:

  • 可以保证依赖非空(字段用final修饰时,构造器必须初始化);
  • 类的依赖关系清晰,通过构造器就能明确知道需要哪些依赖;
  • 测试时不需要反射,直接通过构造器传入Mock或真实实例,更直观;
  • 符合依赖注入原则,类的依赖由外部提供,而非自己获取。

如果你坚持用字段注入,只要测试时正确处理依赖(比如注入真实实例而非Mock),也能正常完成测试,只是相对麻烦一些。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 11:48:34