Issue
I have a problem when testing a method with using passwordencoder:
Cannot invoke "org.springframework.security.crypto.password.PasswordEncoder.encode(java.lang.CharSequence)" because the return value of "com.store.restAPI.user.UserConfig.passwordEncoder()" is null`
Thats my test class method:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
private UserService underTest;
@Mock
private UserRepository userRepository;
@Mock
private UserConfig userConfig;
@BeforeEach
void setUp(){
underTest = new UserService(userRepository, userConfig);
}
@Test
void itShouldFindAllUsers() {
//when
underTest.findAll();
//then
verify(userRepository).findAll();
}
@Test
void addNewUser() {
//given
User expected = new User(
"[email protected]",
"123"
);
//when
underTest.addNewUser(expected);
//then
ArgumentCaptor<User> userArgumentCaptor =
ArgumentCaptor.forClass(User.class);
verify(userRepository).save(userArgumentCaptor.capture());
User capturedUser = userArgumentCaptor.getValue();
assertThat(capturedUser).isEqualTo(expected);
}
@Test
@Disabled
void loginUser() {
}
}
And thats UserService method that i want to test:
@Service
public class UserService {
private final UserRepository userRepository;
private final UserConfig userConfig;
@Autowired
public UserService(UserRepository userRepository, UserConfig userConfig) {
this.userRepository = userRepository;
this.userConfig = userConfig;
}
public List<User> findAll() {
return userRepository.findAll();
}
public void addNewUser(User user) {
Optional<User> userOptional = userRepository.findUserByEmail(user.getEmail());
if(userOptional.isPresent()){
throw new IllegalStateException("email taken");
}
String hashedPassword = userConfig.passwordEncoder().encode(user.getPassword());
user.setPassword(hashedPassword);
userRepository.save(user);
}
public void loginUser(User user){
Optional<User> userOptional = userRepository.findUserByEmail(user.getEmail());
if(userOptional.isEmpty()){
throw new IllegalStateException("no account under that email");
}
else
if(!userConfig.passwordEncoder().matches(user.getPassword(),
userOptional.get().getPassword())){
throw new IllegalStateException("wrong password");
}
//!userOptional.get().getPassword().equals(user.getPassword())
}
}
Password encoder is a bean in class UserConfig.
@Configuration
public class UserConfig {
@Bean
CommandLineRunner commandLineRunnerUser(UserRepository repository) {
return args -> {
User iza = new User(
"[email protected]",
"$2a$10$U87IFlm9DYXRITUSnfdfDuknz8ijJCcK9UVR4D4kUDu7w13zPuURK"
);
User andrzej = new User(
"[email protected]",
"$2a$10$fmYOxyvWBr47wAg1m/ryy.G4J1PbT2LRj6m7oENkBtEsGocansE9G"
);
User tomek = new User(
"[email protected]",
"$2a$10$chrySvbZSZcje4r3Q0PZv.FrO6/k2WvM42GX3x2EmySZc/dAA2glC"
);
repository.saveAll(
List.of(iza,andrzej,tomek)
);
};
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Do i need to create another method with password encoder inside my test class? I don't know what am i doing wrong. Why does it say that result is null? Does someone know what am i doing wrong?
Solution
In your UserServiceTest
, you're currently mocking UserConfig
. A mock is basically an implementation that has no behavior at all. This means that userConfig.passwordEncoder()
will return null
in your test.
To solve this, you have to tell Mockito what the behavior is of your UserConfig
class. For example:
Mockito.when(userConfig.passwordEncoder()).thenReturn(new BCryptPasswordEncoder());
This also allows you to use a different (eg. a dummy) password encoder in your unit tests.
Another suggestion, unrelated to your question, is that you can directly autowire PasswordEncoder
in your UserService
in stead of autowiring UserConfig
. This works because you annotated UserConfig
with @Configuration
and UserConfig.passwordEncoder()
with @Bean
:
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder; // Change this
// And change the constructor parameter
@Autowired
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder; // And this
}
// The rest of your code...
}
Answered By - g00glen00b
Answer Checked By - Cary Denson (JavaFixing Admin)