如何在单元测试中使用包含子映射器的MapStruct
解决MapStruct子映射器单元测试空指针异常问题
问题场景
你定义了包含子映射器的MapStruct接口:
@Mapper(componentModel = "spring", uses = SpouseDetailMapper.class) public interface StudentDetailMapper { StudentDetailMapper INSTANCE = Mappers.getMapper(StudentDetailMapper.class); StudentDetailDTO toDTO(StudentDetail studentDetail); @InheritInverseConfiguration StudentDetail fromDTO(StudentDetailDTO studentDetailDTO); }
编写StudentDetailService单元测试时,使用Mockito的@Spy处理映射器,却因子映射器未注入抛出空指针:
java.lang.NullPointerException: Cannot invoke "com.ex.project.mappers.SpouseDetailMapper.toDTO(com.ex.project.model.SpouseDetail)" because "this.spouseDetailMapper" is null
测试代码如下:
@ExtendWith(MockitoExtension.class) class StudentDetailServiceTest { @Mock private StudentDetailRepository studentDetailRepository; // ??? // @InjectMocks // private SpouseDetailMapper spouseDetailMapper = Mappers.getMapper(SpouseDetailMapper.class); @Spy private StudentDetailMapper studentDetailMapper = Mappers.getMapper(StudentDetailMapper.class); @InjectMocks private StudentDetailService studentDetailService; @Test void getStudentDetail() { when(studentDetailRepository.findByStudentStudentID(anyString())).thenReturn(Optional.of(studentDetail)); // when(spouseDetailMapper.toDTO(spouseDetail)).thenReturn(spouseDetailDTO); when(studentDetailMapper.toDTO(studentDetail)).thenReturn(studentDetailDTO); StudentDetailDTO result = studentDetailService.getStudentDetail(studentWithDetailID); assertThat(studentDetailDTO).isEqualTo(result); } }
解决方案
方案1:手动注入子映射器到父映射器
通过Mappers.getMapper()获取的映射器实例,MapStruct不会自动注入依赖的子映射器,需手动设置:
@ExtendWith(MockitoExtension.class) class StudentDetailServiceTest { @Mock private StudentDetailRepository studentDetailRepository; @Spy private SpouseDetailMapper spouseDetailMapper = Mappers.getMapper(SpouseDetailMapper.class); @Spy private StudentDetailMapper studentDetailMapper = Mappers.getMapper(StudentDetailMapper.class); @InjectMocks private StudentDetailService studentDetailService; @BeforeEach void setUp() { // 强制转换为MapStruct自动生成的实现类,注入子映射器 ((StudentDetailMapperImpl) studentDetailMapper).setSpouseDetailMapper(spouseDetailMapper); } @Test void getStudentDetail() { when(studentDetailRepository.findByStudentStudentID(anyString())).thenReturn(Optional.of(studentDetail)); // 如需mock子映射器逻辑,可添加以下代码 // when(spouseDetailMapper.toDTO(spouseDetail)).thenReturn(spouseDetailDTO); StudentDetailDTO result = studentDetailService.getStudentDetail(studentWithDetailID); assertThat(studentDetailDTO).isEqualTo(result); } }
注:StudentDetailMapperImpl是MapStruct编译后自动生成的实现类,需确保项目已完成编译。
方案2:借助Spring测试上下文加载映射器
因为你的Mapper设置了componentModel = "spring",可以直接用Spring测试注解自动处理依赖注入:
@SpringBootTest @ExtendWith(MockitoExtension.class) class StudentDetailServiceTest { @Mock private StudentDetailRepository studentDetailRepository; @Autowired private StudentDetailMapper studentDetailMapper; @InjectMocks private StudentDetailService studentDetailService; @Test void getStudentDetail() { when(studentDetailRepository.findByStudentStudentID(anyString())).thenReturn(Optional.of(studentDetail)); StudentDetailDTO result = studentDetailService.getStudentDetail(studentWithDetailID); assertThat(studentDetailDTO).isEqualTo(result); } }
如果需要mock子映射器的行为,可将子映射器的@Autowired替换为@MockBean。
方案3:直接mock父映射器的完整行为
若只需测试Service业务逻辑,无需验证映射规则,可直接mock父映射器的方法,跳过子映射器的调用:
@ExtendWith(MockitoExtension.class) class StudentDetailServiceTest { @Mock private StudentDetailRepository studentDetailRepository; @Mock private StudentDetailMapper studentDetailMapper; @InjectMocks private StudentDetailService studentDetailService; @Test void getStudentDetail() { when(studentDetailRepository.findByStudentStudentID(anyString())).thenReturn(Optional.of(studentDetail)); when(studentDetailMapper.toDTO(studentDetail)).thenReturn(studentDetailDTO); StudentDetailDTO result = studentDetailService.getStudentDetail(studentWithDetailID); assertThat(studentDetailDTO).isEqualTo(result); } }
内容的提问来源于stack exchange,提问作者vunhatchuong
相关产品推荐
相关产品推荐

