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

如何在单元测试中使用包含子映射器的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 12:57:16