
本文介绍如何在不启动 spring 上下文的前提下,在 mockito 单元测试中真正调用 mapstruct 自动生成的映射器方法,避免因误用 `@mock` 或 `calls_real_methods` 导致的空返回、npe 或映射失效问题。
在基于 MapStruct 的项目中,开发者常希望通过单元测试验证 DTO 与实体之间的真实映射逻辑(如字段转换、日期格式、嵌套对象处理等),而非仅测试桩(stub)行为。但若错误地将 @Mapper 接口用 @Mock 注解,即使配置 Answers.CALLS_REAL_METHODS,也无法触发 MapStruct 生成的实际实现类——因为 Mockito 的 CALLS_REAL_METHODS 仅对已有具体方法生效,而接口本身无实现体,最终导致 myMapper.listOfCardEntitiesToCardDto(...) 返回 null 或空集合(如示例中 hasSize(4) 断言失败)。
✅ 正确做法是:使用 @Spy + Mappers.get() 主动获取并代理真实的 Mapper 实例:
@ExtendWith(MockitoExtension.class)
public class UserCardServiceTest {
@Mock
private MyCardRepository myCardRepository;
@Spy // 关键:Spy 包装真实实例,允许调用实际方法,同时支持部分 stub
private MyMapper myMapper = Mappers.get(MyMapper.class); // ✅ 获取 MapStruct 生成的真实实现
@InjectMocks
private MyServiceImpl underTest;
// ... 其他 setup 和 test 方法保持不变
}? 原理说明:Mappers.get(MyMapper.class) 会通过 ServiceLoader 查找 MyMapperImpl(MapStruct 编译时生成的默认实现类),并返回其实例。@Spy 则对该实例进行“部分模拟”——未显式 stub 的方法均执行真实逻辑,完美满足“调用真实映射”的需求。
⚠️ 注意事项:
- 不要使用 @Mock(answer = Answers.CALLS_REAL_METHODS):接口无实现,CALLS_REAL_METHODS 无效,且可能掩盖 NullPointerException;
- 确保 MyMapper 已正确声明为 @Mapper 并完成编译:检查 target/classes/.../MyMapperImpl.class 是否存在;
- 避免 @Autowired 或 @Mapper 字段注入:纯单元测试中无 Spring 容器,依赖 Mappers.get() 是最轻量、最可靠的方式;
- 若 Mapper 含 @Mapper(componentModel = "spring"),则需改用 @Import(MyMapperImpl.class) + @Autowired,但此时已属集成测试范畴,违背轻量单元测试初衷。
? 进阶提示:如需对 Mapper 的个别方法做定制化 stub(例如模拟某个复杂字段的异常转换),可结合 doReturn(...).when(myMapper).someMethod(...),@Spy 完全支持此类操作。
综上,仅需两处关键修改即可让测试真正驱动 MapStruct 映射逻辑:
- 将 @Mock 改为 @Spy;
- 初始化时通过 Mappers.get(MyMapper.class) 显式获取实现实例。
无需启动 Spring Boot Context,零额外依赖,完全兼容标准 JUnit 5 + Mockito 测试环境。










