When trying to test a method, I'm getting a NullPointerException
in this line of code:
when(webConfig.passwordEncoder().encode(any())).thenReturn(userUpdated.getPassword());
java.lang.NullPointerException: Cannot invoke "org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.encode(java.lang.CharSequence)" because the return value of "com.example.paymybuddy.security.WebConfig.passwordEncoder()" is null
I have provided a mock of WebConfig
class but it looks like still missing something. Thank you for your help in advance.
My code:
@ExtendWith(MockitoExtension.class)
public class Test {
@Mock
SecurityService securityService;
@Mock
IUserRepository userRepository;
@Mock
WebConfig webConfig;
@InjectMocks
UserService userService;
@Test
public void updateProfileTest() {
UserProfileDTO userDTO = new UserProfileDTO();
userDTO.setEmail("john@simons");
userDTO.setFirstName("John");
userDTO.setLastName("Simons");
userDTO.setPassword("pass");
userDTO.setConfirmPassword("pass");
User userForUpdate = new User();
userForUpdate.setFirstName("Joo");
userForUpdate.setLastName("Sim");
userForUpdate.setBalance(242.20);
userForUpdate.setEmail("john@simons");
userForUpdate.setPassword("pass");
userForUpdate.setRole("ROLE_USER");
User userUpdated = new User();
userUpdated.setFirstName("John");
userUpdated.setLastName("Simons");
userUpdated.setBalance(242.20);
userUpdated.setEmail("john@simons");
userUpdated.setPassword("pass");
userUpdated.setRole("ROLE_USER");
when(userRepository.findByEmail(any())).thenReturn(userForUpdate);
when(webConfig.passwordEncoder().encode(any())).thenReturn(userUpdated.getPassword());
when(userRepository.save(any())).thenReturn(userUpdated);
User test = userService.updateProfile(userDTO);
assertEquals("john@simons",test.getEmail());
}
}
@Service
public class UserService {
@Autowired
private SecurityService securityService;
@Autowired
private IUserRepository userRepository;
@Autowired
private WebConfig webConfig;
public User updateProfile (UserProfileDTO userProfileDTO) {
User user = userRepository.findByEmail(securityService.getLoggedUser());
user.setFirstName(userProfileDTO.getFirstName());
user.setLastName(userProfileDTO.getLastName());
user.setEmail(securityService.getLoggedUser());
if(!userProfileDTO.getConfirmPassword().equals(userProfileDTO.getPassword())){
throw new PasswordException("Password confirmation not match");
}
user.setPassword(webConfig.passwordEncoder().encode(userProfileDTO.getPassword()));
return userRepository.save(user);
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
@Bean
RequestRejectedHandler requestRejectedHandler() {
return new HttpStatusRequestRejectedHandler();
}
}
CodePudding user response:
This line is the problem (or more broadly your design but more on that later). webConfig
is a mock, which means it will intercept all method calls and excert the registered behavior or the default if none. webConfig.passwordEncoder()
is the first call on yuor mock and you haven't told the mock what to do, hence it will do the default which is to return null
. You go then on by mocking the encode
call on the return value, which will fail.
You should either tell Mockito to do something else by default (@Mock(answer = Answers.RETURNS_DEEP_STUBS
instead of plain @Mock
) or explicitly mock the call to webConfig.passwordEncoder()
and return a mocked PasswordEncoder
and then mock behavior on that for encode
.
However the fact that you inject the WebConfig
and not a PasswordEncoder
in your UserService
is the actual problem (or the problem in your design). You should inject a PasswordEncoder
.
@Service
public class UserService {
private final SecurityService securityService;
private final IUserRepository userRepository;
private final PasswordEncoder encoder;
public UserService(SecurityService securityService, IUserRepository userRepository, PasswordEncoder encoder) {
this.securityService=securityService;
this.userRepository=userRepository;
this.encoder=encoder;
}
public User updateProfile (UserProfileDTO userProfileDTO) {
User user = userRepository.findByEmail(securityService.getLoggedUser());
user.setFirstName(userProfileDTO.getFirstName());
user.setLastName(userProfileDTO.getLastName());
user.setEmail(securityService.getLoggedUser());
if(!userProfileDTO.getConfirmPassword().equals(userProfileDTO.getPassword())){
throw new PasswordException("Password confirmation not match");
}
user.setPassword(encoder.encode(userProfileDTO.getPassword()));
return userRepository.save(user);
}
}
Now you can modify your test as well.
@ExtendWith(MockitoExtension.class)
public class Test {
@Mock
SecurityService securityService;
@Mock
IUserRepository userRepository;
@Mock
PasswordEncoder encoder;
@InjectMocks
UserService userService;
@Test
public void updateProfileTest() {
UserProfileDTO userDTO = new UserProfileDTO();
userDTO.setEmail("john@simons");
userDTO.setFirstName("John");
userDTO.setLastName("Simons");
userDTO.setPassword("pass");
userDTO.setConfirmPassword("pass");
User userForUpdate = new User();
userForUpdate.setFirstName("Joo");
userForUpdate.setLastName("Sim");
userForUpdate.setBalance(242.20);
userForUpdate.setEmail("john@simons");
userForUpdate.setPassword("pass");
userForUpdate.setRole("ROLE_USER");
User userUpdated = new User();
userUpdated.setFirstName("John");
userUpdated.setLastName("Simons");
userUpdated.setBalance(242.20);
userUpdated.setEmail("john@simons");
userUpdated.setPassword("pass");
userUpdated.setRole("ROLE_USER");
when(userRepository.findByEmail(any())).thenReturn(userForUpdate);
when(encoder.encode(any())).thenReturn(userUpdated.getPassword());
when(userRepository.save(any())).thenReturn(userUpdated);
User test = userService.updateProfile(userDTO);
assertEquals("john@simons",test.getEmail());
}
}
Now your service just depends on the classes it needs instead of knowing something about the configuration.
CodePudding user response:
Thanks, everybody, for helping again. I knew it it's something not complicated, but I couldn't figure out how to fix it properly. I changed my Service class, and everything works as it should.