使用ModelMapper时Mockito模拟失效,如何正确Mock?
正确Mock ModelMapper的方法
针对你遇到的问题,核心是ModelMapper的map(source, destination)是**无返回值(void)**的方法,它的作用是修改传入的destination对象,而非返回新对象。Mockito中处理这类方法不能用when().thenReturn(),需要用doAnswer来模拟实际的字段赋值行为。
修正后的测试代码
@ExtendWith(MockitoExtension.class) class UserServiceTest { @InjectMocks private UserServiceImpl service; @Mock private UserRepository repository; @Mock private ModelMapper mapper; @Test void updateWithSuccess() { long userId = 1L; UpdateUserDTO requestUpdateUserDTO = UpdateUserDTO.builder().height(1f).weight(1f).build(); User userFromDB = User.builder().id(1L).ssn("123").height(50f).weight(50f).build(); UserDTO response = UserDTO.builder().id(1L).height(1f).weight(1f).build(); // Mock repository.findById Mockito.when(repository.findById(userId)).thenReturn(Optional.of(userFromDB)); // 模拟ModelMapper的void map方法:手动将UpdateUserDTO的字段赋值给User对象 Mockito.doAnswer(invocation -> { // 获取方法的参数:第一个是UpdateUserDTO,第二个是User UpdateUserDTO source = invocation.getArgument(0); User target = invocation.getArgument(1); target.setHeight(source.getHeight()); target.setWeight(source.getWeight()); return null; // void方法返回null }).when(mapper).map(Mockito.any(UpdateUserDTO.class), Mockito.any(User.class)); // Mock repository.save Mockito.when(repository.save(Mockito.any(User.class))).thenReturn(userFromDB); // Mock map User到UserDTO的方法 Mockito.when(mapper.map(Mockito.any(User.class), Mockito.eq(UserDTO.class))).thenReturn(response); // 执行测试方法 UserDTO result = service.update(userId, requestUpdateUserDTO); // 验证User对象的字段是否被正确更新 assertEquals(requestUpdateUserDTO.getHeight(), userFromDB.getHeight()); assertEquals(requestUpdateUserDTO.getWeight(), userFromDB.getWeight()); // 验证repository.save被调用,且传入的是更新后的User ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class); Mockito.verify(repository).save(userCaptor.capture()); assertEquals(requestUpdateUserDTO.getHeight(), userCaptor.getValue().getHeight()); assertEquals(requestUpdateUserDTO.getWeight(), userCaptor.getValue().getWeight()); } }
关键修正点说明
- 处理void方法的Mock:使用
doAnswer拦截map方法调用,手动实现字段复制逻辑,确保传入的User对象被正确修改。 - 修复测试结构问题:你之前把
service.update和断言代码写在了@Test方法外部,这会导致编译错误,必须将执行和断言逻辑放在测试方法内部。 - 补充缺失的Mock依赖:测试类中漏了
@Mock private UserRepository repository;,这会引发NullPointerException。 - 正确使用ArgumentCaptor:需要通过
verify(repository).save(userCaptor.capture())触发捕获,才能获取到保存的User对象。
替代方案:使用真实的ModelMapper实例
如果你的测试需要验证ModelMapper的映射规则是否正确,可以不用Mock它,直接注入真实实例:
@ExtendWith(MockitoExtension.class) class UserServiceTest { @InjectMocks private UserServiceImpl service; @Mock private UserRepository repository; // 注入真实的ModelMapper private ModelMapper mapper = new ModelMapper(); @BeforeEach void setUp() { service = new UserServiceImpl(repository, mapper); } // 后续测试逻辑无需Mock mapper,直接执行即可验证映射结果 }
内容的提问来源于stack exchange,提问作者Caroline
相关产品推荐
相关产品推荐

