I'm trying to test my service implementation with JUNIT5
. I don't quite understand whats going wrong but my UserRepository
doesn't seem to be returning a value.
I have tested to see if the UserRepository has been used with:
verify(repository, times(1));
And the response was "Wanted but not invoked...Actually, there were zero interactions with this mock"
Here is the Test 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("[email protected]");
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());
}
}
The error that I am getting:
org.mockito.exceptions.misusing.PotentialStubbingProblem:
Strict stubbing argument mismatch. Please check:
- this invocation of 'save' method:
repository.save(
User(userID=0, [email protected], 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 protected], timeCreated=2022-12-14 03:18:24.0235563, password=test, firstName=john, lastName=doe)
);
My Service Class that I'm creating the test for:
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;
}
}
Thanks for reading! :)
CodePudding user response:
I guess that your User
class has an equals
/hashCode
defined either on the userID
field or on all fields. When you mock your repository in your setUp
method, you define what the method should do when it is called with the exact object that you specified, comparing the given object to the object specified in the mock by calling the equals
/hashCode
method.
You define the User
object in your setUp
method to have the userID
1
but in your UserServiceImpl
the userID
is not ever set (as it is generated by the persistence layer). Therefore the equals
/hashCode
check to determine if Mockito should execute the stub logic will never be called because the object passed by the UserServiceImpl
will never be equals to the one that you defined in your mock.
There are several ways how you can solve this.
1) Integration test
My advice would be to convert your Unit-Test to a @SpringBootTest
. There is no value in testing mocked behaviour. If this test was an integration test, you would test that the repository inserts the User
into the database and generates an ID with your assertion.
2) Unit test
If you want to mock the repository, you should use an any()
matcher for the input argument and then do what the repository would do, namely set the id:
when(repository.save(any(User.class))).thenAnswer(invocation -> {
final User entity = invocation.getArgument(0);
ReflectionTestUtils.setField(entity, "userID", RandomUtils.nextLong());
return entity;
});
I would then assert, that the userID
is not null, as the userID
will be randomly generated as it would in production:
@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("[email protected]");
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();
}
}
I highly recommend to use AssertJ for your assertions as it offers fluent asssertions and way more assertion methods than JUnit.