Mockito测试中UserRepository存根参数不匹配致返回Null求助
JUnit5测试UserServiceImpl时Mockito存根匹配失败及交互验证问题排查
问题现象
使用JUnit5 + Mockito测试UserServiceImpl时,出现以下问题:
- UserRepository无法返回预期值
- 调用
verify(repository, times(1))验证时,提示Mock对象未产生任何交互 - 测试触发
Strict stubbing argument mismatch错误
测试类代码
@ExtendWith(MockitoExtension.class) public class UserServiceTest { @Mock UserRepository repository; @InjectMocks UserServiceImpl userServiceImpl; UserModel inputUserModel; @BeforeEach public void setUp() throws Exception { inputUserModel = new UserModel(); inputUserModel.setEmail("testemail@gmail.com"); inputUserModel.setFirstName("john"); inputUserModel.setLastName("doe"); inputUserModel.setPassword("test"); inputUserModel.setMatchPassword("test"); User inputUser = User.builder() .email(inputUserModel.getEmail()) .firstName(inputUserModel.getFirstName()) .lastName(inputUserModel.getLastName()) .password(inputUserModel.getPassword()) .timeCreated(Timestamp.valueOf(LocalDateTime.now(ZoneId.systemDefault()))) .userID(1) .build(); User outputUser = User.builder() .email(inputUserModel.getFirstName()) .password(inputUserModel.getLastName()) .lastName(inputUserModel.getFirstName()) .firstName(inputUserModel.getFirstName()) .timeCreated(Timestamp.valueOf(LocalDateTime.now(ZoneId.systemDefault()))) .userID(1) .build(); Mockito.when(repository.save(inputUser)).thenReturn(outputUser); } @Test public void whenSaveUser_ThenUserHasID(){ Assertions.assertEquals(1, userServiceImpl.saveUser(inputUserModel).getUserID()); } }
报错信息
org.mockito.exceptions.misusing.PotentialStubbingProblem: Strict stubbing argument mismatch. Please check: - this invocation of 'save' method: repository.save( User(userID=0, email=testemail@gmail.com, timeCreated=2022-12-14 03:18:24.0435578, password=test, firstName=john, lastName=doe) ); -> at com.jschwery.securitydemo.service.Implementations.UserServiceImpl.saveUser(UserServiceImpl.java:37) - has following stubbing(s) with different arguments: 1. repository.save( User(userID=1, email=testemail@gmail.com, timeCreated=2022-12-14 03:18:24.0235563, password=test, firstName=john, lastName=doe) );
待测试服务类代码
public class UserServiceImpl implements UserService { UserRepository userRepository; @Autowired public UserServiceImpl(UserRepository repository){ this.userRepository = repository; } @Override public User saveUser(UserModel userModel) { if(!Objects.equals(userModel.getPassword(), userModel.getMatchPassword())){ throw new UserException("Passwords do not match"); } User user = User.builder() .email(userModel.getEmail()) .firstName(userModel.getFirstName()) .lastName(userModel.getLastName()) .password(userModel.getPassword()) .timeCreated(Timestamp.valueOf(LocalDateTime.now(ZoneId.systemDefault()))) .build(); User returnedUser = userRepository.save(user); System.out.println(returnedUser.getEmail()); System.out.println("userID" + returnedUser.getUserID()); return returnedUser; } }
错误原因分析
从报错信息可明确参数不匹配的核心问题:
- UserID不一致:测试类存根的
inputUser手动设置了userID=1,但服务类中创建的User对象未指定userID,默认值为0 - TimeCreated不一致:测试类存根时的
timeCreated是setUp方法执行时的时间,服务类创建User时的timeCreated是saveUser方法执行时的时间,两次时间必然不同 - Mockito默认使用对象的
equals方法匹配参数,以上两个差异导致存根的save方法无法匹配实际调用,进而引发存根失效、交互验证失败
修复方案
方案1:使用参数匹配器忽略无关属性
将存根逻辑改为使用Mockito参数匹配器,只匹配关键业务属性,忽略userID和timeCreated这类自动生成的属性:
// 替换原存根代码 Mockito.when(repository.save(Mockito.argThat(user -> user.getEmail().equals(inputUserModel.getEmail()) && user.getFirstName().equals(inputUserModel.getFirstName()) && user.getLastName().equals(inputUserModel.getLastName()) && user.getPassword().equals(inputUserModel.getPassword()) ))).thenReturn(outputUser);
如果不需要精确匹配属性,也可以直接用any(User.class)简化:
Mockito.when(repository.save(Mockito.any(User.class))).thenReturn(outputUser);
方案2:统一User对象创建逻辑(可选)
将服务类中的User创建逻辑提取为可复用方法,测试时直接复用该方法创建匹配的User对象:
// 服务类中添加方法 protected User buildUserFromModel(UserModel userModel) { return User.builder() .email(userModel.getEmail()) .firstName(userModel.getFirstName()) .lastName(userModel.getLastName()) .password(userModel.getPassword()) .timeCreated(Timestamp.valueOf(LocalDateTime.now(ZoneId.systemDefault()))) .build(); } // 测试类中调用该方法创建inputUser User inputUser = userServiceImpl.buildUserFromModel(inputUserModel); // 此时inputUser的userID为0,和服务类创建的对象一致 Mockito.when(repository.save(inputUser)).thenReturn(outputUser);
注意:这种方式需要处理timeCreated的差异,比如用固定时间替代
LocalDateTime.now(),或者在User类中重写equals方法忽略timeCreated属性
方案3:修复verify验证(如果需要)
如果要验证save方法被调用,同样需要使用参数匹配器:
@Test public void whenSaveUser_ThenUserHasID(){ User result = userServiceImpl.saveUser(inputUserModel); Assertions.assertEquals(1, result.getUserID()); // 使用参数匹配器验证交互 Mockito.verify(repository, Mockito.times(1)).save(Mockito.any(User.class)); }
修复后的完整测试类示例
@ExtendWith(MockitoExtension.class) public class UserServiceTest { @Mock UserRepository repository; @InjectMocks UserServiceImpl userServiceImpl; UserModel inputUserModel; @BeforeEach public void setUp() throws Exception { inputUserModel = new UserModel(); inputUserModel.setEmail("testemail@gmail.com"); inputUserModel.setFirstName("john"); inputUserModel.setLastName("doe"); inputUserModel.setPassword("test"); inputUserModel.setMatchPassword("test"); User outputUser = User.builder() .email(inputUserModel.getFirstName()) .password(inputUserModel.getLastName()) .lastName(inputUserModel.getFirstName()) .firstName(inputUserModel.getFirstName()) .timeCreated(Timestamp.valueOf(LocalDateTime.now(ZoneId.systemDefault()))) .userID(1) .build(); // 使用参数匹配器匹配关键属性 Mockito.when(repository.save(Mockito.argThat(user -> user.getEmail().equals(inputUserModel.getEmail()) && user.getFirstName().equals(inputUserModel.getFirstName()) && user.getLastName().equals(inputUserModel.getLastName()) && user.getPassword().equals(inputUserModel.getPassword()) ))).thenReturn(outputUser); } @Test public void whenSaveUser_ThenUserHasID(){ User result = userServiceImpl.saveUser(inputUserModel); Assertions.assertEquals(1, result.getUserID()); // 验证save方法被调用一次 Mockito.verify(repository, Mockito.times(1)).save(Mockito.any(User.class)); } }
内容的提问来源于stack exchange,提问作者Jschwery
相关产品推荐
相关产品推荐

