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

运行单元测试时Service类中MapStruct映射返回空数组问题

问题场景

编写单元测试用例时,在Service类的业务方法中使用MapStruct实现实体类到DTO类的映射转换。

Mapper类定义

@Mapper(componentModel = "spring")
public interface UserMapper {
    List<UserDto> toUserDto(List<UserEntity> users);
}

Service类注入方式

通过构造器注入UserMapper实例:

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService{
    private final UserMapper userMapper;
}

业务逻辑映射调用

List<UserDto> userDto = userMapper.toUserDto(lst);

测试类代码

@RunWith(MockitoJUnitRunner.class)
@SpringBootTest(classes = Application.class)
public class ApplicationTest {
    
    @Mock
    private UserRepository userRepo;
    
    @Mock
    private UserMapper userMapper;
    
    @InjectMocks
    private UserServiceImpl userServiceImpl;
    
    @Test
    public void contextLoads() {
        then(controller).isNotNull();
        then(userServiceImpl).isNotNull();
    }
    
    @Test
    public void getAllUser() {
        List<UserEntity> lst = new ArrayList<UserEntity>();
        UserEntity userOne = new UserEntity();
        userOne.setEmpFullname("Test Test1");
        userOne.setUserId("marina");
        userOne.setFlag("Y");
        UserEntity usertwo = new UserEntity();
        usertwo.setEmpFullname("Test Test2");
        usertwo.setUserId("test");
        usertwo.setFlag("Y");
        lst.add(userOne);
        lst.add(usertwo);
        
        when(userRepo.findByStatus("W")).thenReturn(lst);
        try {
            List<UserDto> pendingUsersList = userServiceImpl.getPendingUser();
            assertEquals(2, pendingUsersList.size());
            
        } catch (GeneralException e) {
            e.printStackTrace();
        }   
    }
}

异常现象

运行测试用例时,代码可以正常获取到构造的2条UserEntity实体记录,但执行映射语句List<UserDto> userDto = userMapper.toUserDto(lst);时方法返回空白数组,导致测试断言失败。
备注:UserEntity实体类包含多个字段,本次测试用例中仅为其中3个字段赋值。

问题根因
  • 核心问题是测试类中用@Mock标记了UserMapper:Mockito生成的Mock对象默认会对所有方法返回类型匹配的默认值,集合类型的默认返回值就是空集合,根本不会执行MapStruct生成的真实映射逻辑,自然拿不到正确的转换结果。
  • 测试类注解配置冲突:@RunWith(MockitoJUnitRunner.class)是纯Mockito单元测试的运行器,不会启动Spring容器;@SpringBootTest是Spring集成测试注解,需要搭配Spring测试运行器使用,两者混用会导致依赖注入逻辑异常。
  • 备注中提到的「仅给3个实体字段赋值」不会导致空集合问题:未赋值的字段会按Java类型默认值映射到DTO,不会影响返回集合的元素个数。
解决方案

根据要写的测试类型二选一即可:

  • 纯单元测试(不启动Spring,执行速度快)
    1. 移除测试类上的@SpringBootTest注解,保留@RunWith(MockitoJUnitRunner.class)
    2. 不要Mock UserMapper,直接通过MapStruct提供的Mappers工具类获取真实的Mapper实现,交给Mockito注入到被测Service中,修改后测试类的依赖部分代码如下:
@RunWith(MockitoJUnitRunner.class)
public class ApplicationTest {
    
    @Mock
    private UserRepository userRepo;
    
    // 直接获取真实的MapStruct映射实现,不要加@Mock
    private UserMapper userMapper = Mappers.getMapper(UserMapper.class);
    
    @InjectMocks
    private UserServiceImpl userServiceImpl;

    // 测试方法逻辑保持不变
}
  • Spring集成测试(启动Spring容器,加载完整上下文)
    1. 把类上的@RunWith(MockitoJUnitRunner.class)替换为@RunWith(SpringRunner.class)
    2. UserRepository用@MockBean标记实现容器内的Mock替换,UserMapper不要Mock,直接通过@Autowired注入容器中真实的MapStruct实现,被测Service也直接从容器注入,修改后依赖部分代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ApplicationTest {
    
    @MockBean
    private UserRepository userRepo;
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private UserServiceImpl userServiceImpl;

    // 测试方法逻辑保持不变
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 14:15:52