Spring Boot中BookService单元测试遇Mockito报错、映射异常如何解决
核心问题根源
所有被@Mock注解标记的对象,都是Mockito生成的无业务逻辑的假对象。
- 没有预先通过
when()打桩配置的mock方法,调用时只会返回对应类型的默认空值(引用类型返回null、数值类型返回0、布尔类型返回false) - 这类假对象绝对不会执行真实的业务逻辑
你所有测试报错的核心,都是因为直接调用了未打桩的mock方法,拿到了null值后引发了后续连锁错误。
分场景问题拆解
1. addNewBook测试触发Mockito打桩格式错误的原因
你在测试代码中直接执行了book = modelMapper.map(bookDto, Book.class),但modelMapper是mock对象,没有提前打桩的情况下这行调用的返回值是null,直接给book变量赋了null。
后续你写Mockito.when(bookRepository.save(book)).thenReturn(book)时,Mockito的严格模式检测到你在打桩语句内部嵌套调用了另一个mock的方法、且没有给这个嵌套方法配置返回值,直接触发了提示打桩格式错误的异常。
正确的打桩顺序必须是先配置mock的返回规则,再调用业务方法做断言,示例:
// 提前配置ModelMapper的转换规则:传入bookDto转Book类时,返回预先初始化好的book实体 Mockito.when(modelMapper.map(bookDto, Book.class)).thenReturn(book); // 提前配置Repository的保存规则:传入指定book实体时,返回同一个实体 Mockito.when(bookRepository.save(book)).thenReturn(book); // 调用业务方法执行测试 Book result = bookService.addNewBook(bookDto); // 做结果断言 assertEquals(book, result);
2. findBookById测试提示图书不存在的原因
- 同样是未给
modelMapper.map(bookDto, Book.class)打桩,方法返回null,你给bookRepository.findById(2L)打桩时,返回的Optional包装的就是这个null实体。Service层拿到null的查询结果,自然会触发“图书不存在”的业务分支。 - 如果项目里的
findBookById方法逻辑是“查找到实体后,再通过ModelMapper转回BookDto返回”,你还需要额外配置实体转DTO方向的打桩规则,否则最终返回给断言的对象也会是null。 - 测试配置问题:你当前的测试类同时标注了
@SpringBootTest、@RunWith(SpringRunner.class),还搭配了纯Mockito的mock注解,会强制加载整个Spring应用上下文,完全偏离了单元测试的轻量性要求。纯单元测试只需要使用@ExtendWith(MockitoExtension.class)注解即可,不需要加载Spring容器。
3. allBooks测试抛出java.lang.IllegalArgumentException: source cannot be null的原因
- 第一处代码笔误:给
bookDto1设置rented属性时,错写成了bookDto.setRented(2),导致bookDto1的该字段没有被正确赋值。 - 核心触发逻辑:你调用
modelMapper.map(bookDto, Book.class)时没有打桩,返回值是null,你将这个null作为元素放入bookList,再把这个包含null的列表打桩给bookRepository.findAll()。后续Service层遍历列表时,会把每个元素传给ModelMapper做实体转DTO的操作,此时传给ModelMapper的source就是之前的null值,直接触发了ModelMapper内置的空参数校验,抛出source cannot be null的异常。 - 断言逻辑错误:
allBooks方法返回的是DTO列表,你却拿单个bookDto对象跟返回值做相等断言,类型完全不匹配。
内容的提问来源于stack exchange,提问作者user9347049
相关产品推荐
相关产品推荐

