运行单元测试时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,执行速度快)
- 移除测试类上的
@SpringBootTest注解,保留@RunWith(MockitoJUnitRunner.class) - 不要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容器,加载完整上下文)
- 把类上的
@RunWith(MockitoJUnitRunner.class)替换为@RunWith(SpringRunner.class) - 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
相关产品推荐
相关产品推荐

