I have a class UserService that i testing. Here it is:
@Service
public class UserService implements UserDetailsService {
@Autowired
UserDao userDao;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
User user = userDao.loadUserByLogin(login);
if (user == null) {
throw new UsernameNotFoundException("User with login: " login " not found");
}
Set<GrantedAuthority> authorities = new HashSet<>();
for (Role role: user.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPassword(), authorities);
}
public User findUserByLogin(String login) {
User user = userDao.loadUserByLogin(login);
return user;
}
public User findByLoginAndPassword(String login, String password) {
User user = findUserByLogin(login);
if (user != null) {
if (passwordEncoder.matches(password, user.getPassword())) {
return user;
}
}
return null;
}
}
And I have a problem to write unit test from method public User findByLoginAndPassword(String login, String password)
This is my test class:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = TestConfig.class, loader = AnnotationConfigContextLoader.class)
public class UserServiceTest {
private User getTestUser(){
Set<Role> roles = new HashSet<>(Arrays.asList(new Role(1L, "ROLE_CLIENT")));
return new User(1, "test login", "test name" , "test password", roles);
}
@Autowired
UserDao userDao;
@Autowired
UserService userService;
@Autowired
PasswordEncoder passwordEncoder;
// Test from method UserService.loadUserByUsername
// if we get from userDao null then should throw UserNotFoundException, if user not null then we get userDetails
// instance with 1 authority
@Test
public void loadUserByUsernameTest(){
when(userDao.loadUserByLogin(any())).thenReturn(null);
Assertions.assertThrows(UsernameNotFoundException.class, () -> userService.loadUserByUsername(any()));
when(userDao.loadUserByLogin(any())).thenReturn(getTestUser());
Assertions.assertEquals(userService.loadUserByUsername(any()).getAuthorities().size(), 1);
}
// Test from method UserService.findUserByLogin
// if we request user then we get test user with same login and method userDao.loadUserByLogin invoke once
@Test
public void findUserByLogin(){
when(userDao.loadUserByLogin(any())).thenReturn(getTestUser());
Assertions.assertEquals(userDao.loadUserByLogin(any()).getLogin(), getTestUser().getLogin());
verify(userDao).loadUserByLogin(any());
}
// Test from method UserService.findByLoginAndPassword
// if we request user then we get test user with same login and methods userDao.loadUserByLogin and
// passwordEncoder.matches invoke once
@Test
public void findByLoginAndPassword(){
when(userDao.loadUserByLogin(any())).thenReturn(getTestUser());
when(passwordEncoder.matches(any(), any())).thenReturn(true);
//below is line 71!
Assertions.assertEquals(userService.findByLoginAndPassword(any(), any()).getLogin(), getTestUser().getLogin());
verify(userDao).loadUserByLogin(any());
verify(passwordEncoder).matches(any(), any());
}
}
I also have TestConfig.class:
@Configuration
public class TestConfig {
@Bean
public UserService userService(){
return new UserService();
}
@Bean
public UserDao userDao(){
return mock(UserDao.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
return mock(PasswordEncoder.class);
}
}
And when i start test from findByLoginAndPassword(), i have InvalidUseOfMatchersException:
Invalid use of argument matchers!
1 matchers expected, 2 recorded:
-> at service.UserServiceTest.findByLoginAndPassword(UserServiceTest.java:71)
-> at service.UserServiceTest.findByLoginAndPassword(UserServiceTest.java:71)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
For more info see javadoc for Matchers class.
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
1 matchers expected, 2 recorded:
-> at service.UserServiceTest.findByLoginAndPassword(UserServiceTest.java:71)
-> at service.UserServiceTest.findByLoginAndPassword(UserServiceTest.java:71)
I understand, then problem in my 2 String arguments, but i can`t resolve this. I tried use (any(), any()) or (any(), anyString()), or (any(), eq(eny())) or any combination of these options. Please, help my and please, explain to me why this is happening??
CodePudding user response:
In the offending line, Assertions.assertEquals(userService.findByLoginAndPassword(any(), any()).getLogin(), getTestUser().getLogin());
, you are calling the service under test, i.e. the userService
, passing it the result of any()
. This is not appropriate usage of a matcher, it has to be used with a mocking call (when(...)
) or a verification call. In this place you can use any 2 strings and, with the current test setup, they will match - e.g. Assertions.assertEquals(userService.findByLoginAndPassword("bob", "123").getLogin(), getTestUser().getLogin());
.
Going one step further though, this test setup can be improved. You see, you may pass the user name "bob" to the method, but even if it passes "patrick" to findUserByLogin(login)
the test will succeed, because you have configured any()
in the line: when(userDao.loadUserByLogin(any())).thenReturn(getTestUser())
. Consider an alternative:
@Test
public void findByLoginAndPassword(){
when(userDao.loadUserByLogin("test login")).thenReturn(getTestUser());
when(passwordEncoder.matches("123", "test password")).thenReturn(true);
Assertions.assertEquals(userService.findByLoginAndPassword("test login", "123").getLogin(), getTestUser().getLogin());
//
// The verifications below are redundant with the new code
// if e.g. passwordEncoder.matches() is not called with the configured
// arguments, Mockito will catch it and report it as an error,
// unless you have explicitly configured it not to (lenient mocks)
//
// verify(userDao).loadUserByLogin("test login");
// verify(passwordEncoder).matches("123", "test password");
}