我正在尝试用JUNIT5
测试我的服务实现。我不太明白出了什么问题,但我的UserRepository
似乎没有返回值。
我已经测试了UserRepository是否与以下内容一起使用:CCD_ 3;通缉但未调用。。。事实上,与这个mock";
这是测试类:
@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;
}
}
感谢阅读!:)
我猜您的User
类在userID
字段或所有字段上都定义了equals
/hashCode
。当您在setUp
方法中模拟存储库时,您可以定义当使用您指定的精确对象调用该方法时该方法应该做什么,通过调用equals
/hashCode
方法将给定对象与模拟中指定的对象进行比较。
您在setUp
方法中定义了User
对象,使其具有userID
1
,但在UserServiceImpl
中,userID
永远不会设置(因为它是由持久层生成的)。因此,确定Mockito是否应该执行存根逻辑的equals
/hashCode
检查永远不会被调用,因为UserServiceImpl
传递的对象永远不会等于您在mock中定义的对象。
有几种方法可以解决这个问题。
1) 集成测试
我的建议是将您的单元测试转换为@SpringBootTest
。测试被嘲笑的行为没有任何价值。如果这个测试是一个集成测试,那么您将测试存储库是否将User
插入到数据库中,并使用您的断言生成一个ID。
2) 单元测试
如果您想模拟存储库,您应该使用any()
匹配器作为输入参数,然后执行存储库的操作,即设置id:
when(repository.save(any(User.class))).thenAnswer(invocation -> {
final User entity = invocation.getArgument(0);
ReflectionTestUtils.setField(entity, "userID", RandomUtils.nextLong());
return entity;
});
然后我断言,userID
不是空的,因为userID
将像在生产中一样随机生成:
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository repository;
@InjectMocks
private UserServiceImpl userServiceImpl;
@Test
void saveUser_userHasID(){
// Arrange
final UserModel inputUserModel = new UserModel();
inputUserModel.setEmail("testemail@gmail.com");
inputUserModel.setFirstName("john");
inputUserModel.setLastName("doe");
inputUserModel.setPassword("test");
inputUserModel.setMatchPassword("test");
when(repository.save(any(User.class))).thenAnswer(invocation -> {
final User entity = invocation.getArgument(0);
ReflectionTestUtils.setField(entity, "userID", RandomUtils.nextLong());
return entity;
});
// Act
final User user = userServiceImpl.saveUser(inputUserModel);
// Assert
assertThat(user.getUserID()).isNotNull();
}
}
我强烈建议在断言中使用AssertJ,因为它提供了流畅的断言和比JUnit多得多的断言方法
tl;dr
Spring数据无法通过带有mock的简单单元测试生成userID,您需要在Integration测试中运行Spring上下文。
详细信息
有两个字段导致出现错误:UserID
和timeCreated
。
- 对于
timeCreated
字段:在测试中创建一个inputUser
用于模拟的对象。问题是这个物体inputUser
不是测试中的方法所使用的CCD_29verify(repository, times(1));
0你可以清楚地看到时间戳在两个对象之间的错误方面不同 - 对于
UserID
字段:再次给出您的期望,您的mock将使用32对象。此对象的userID=1。但在现实中方法saveUser(UserModel userModel)
未设置此字段因此默认值将设置为0
,您可以看到也在错误中
现在,由于您的测试用例whenSaveUser_ThenUserHasID
旨在验证保存的记录是否具有ID,因此您不能使用mock来完成此操作。ID是由您正在使用的Spring Data生成的,要使其运行,您应该使用和Integration test在测试中运行您的Spring上下文,以便您的Spring数据能够为用户生成ID,这是前面的一个问题,对您这样做很有帮助。有了这个问题,您既不需要测试中的inputUser
,也不需要mock。(您可以使用JUnit 5中的@DataJpaTest
或@SpringBootTest
…)