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();
}
}
when(webConfig.passwordEncoder().encode(any())).thenReturn(userUpdated.getPassword());
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.
Related
I have a service class, called CommandUser with 4 dependencies (3 repositories and one passwordEncoder) and a respective test class, called CommandUserTest. I instantiate the 3 dependencies using #Autowired and the passwordEncoder using #RequiredArgsConstruct.
The problem happens when I run the createUser test, the 3 repositories mocks are null and only the passwordEncoder mock were initialized properly. I think the problem is related with use of different dependecy injections approaches (#Autowired and #RequiredArgsConstructor).
One solution is use #Autowired in passwordEncoder dependency (all dependencies using #Autowired) or all dependencies using #RequiredArgsConstructor, but I wanna understand why this happens.
Thanks in advance.
The service class (CommandUser):
#Service
#RequiredArgsConstructor
#Transactional
public class CommandUser implements ICommandUser, UserDetailsService {
#Autowired
private IQueryRoleRepository queryRoleRepository;
#Autowired
private ICommandUserRepository commandUserRepository;
#Autowired
private IQueryUserRepository queryUserRepository;
private final PasswordEncoder passwordEncoder;
#Override
public User createUser(User user) throws GeneralBusinessException {
Optional<User> optionalUser = queryUserRepository.findUserByUsername(user.getUsername());
if(optionalUser.isPresent()) throw UserAlreadyExistsException.builder().username(user.getUsername()).build();
user.setPassword(passwordEncoder.encode(user.getPassword()));
return commandUserRepository.saveUser(user);
}
// other methods;
}
The test class (CommandUserTest):
#ExtendWith(MockitoExtension.class)
class CommandUserTest {
#Mock
private IQueryRoleRepository queryRoleRepository;
#Mock
private ICommandUserRepository commandUserRepository;
#Mock
private IQueryUserRepository queryUserRepository;
#Mock
private PasswordEncoder passwordEncoder;
#InjectMocks
private CommandUser commandUser;
#Test
void createUser() throws GeneralBusinessException {
// given
User user = factoryDummyUser();
// when
commandUser.createUser(user);
// then
verify(commandUserRepository).saveUser(user);
}
// other tests
}
Currently I have an endpoint in my controller that calls different methods from my service. Each of these methods recieve a parameter called executionId that is different for every http request.
#RestController
public class ProcedureController {
#Autowired
MyService service;
#GetMapping
public void execute(){
String executionId = ...; //randomly generated
service.executeMethodA(executionId);
service.executeMethodB(executionId);
}
}
#Service
public class MyService {
public void executeMethodA(String executionId){
...
}
public void executeMethodB(String executionId){
...
}
}
It works fine but it seems very repetitive to pass executionId as a parameter to each method. So one solution that I thought was to instantiate my service with the executionId so it can hold this information and every method can access it.
#RestController
public class ProcedureController {
#GetMapping
public void execute(){
String executionId = ...; //randomly generated
MyService service = new MyService(executionId);
service.executeMethodA();
service.executeMethodB();
}
}
#Service
public class MyService {
String executionId;
public MyService(String executionId){
this.executionId = executionId;
}
public void executeMethodA(){
// Use executionId here
...
}
public void executeMethodB(){
// Use executionId here
...
}
}
Is this solution a bad practice? If so, how can I achieve something similar?
Spring allows you to inject a managed object (bean) as a dependency into another object via the #Autowired annotation.
For example, if I have a UserService that has a dependency on UserRepository, I can have the UserRepository injected using #Autowired annotation like this:
UserRepository Class
class UserRepository {
UserRepository () {}
}
UserService Class
class UserService {
#Autowired
private UserRepository userRepository;
UserService () {}
}
This is done using Field Injection. The same thing can be accomplice using Setter Injection:
class UserService {
private UserRepository userRepository;
UserService () {}
#Autowired // Using setter injection
public void setUserRepository(
UserRepository userRepository) {
this.userRepository = userRepository
}
}
or via Constructor Injection:
class UserService {
private UserRepository userRepository;
#Autowired // Using constructor Injection
UserService (UserRepository userRepository) {
this.userRepository = userRepository
}
}
public class UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private ConverterService converterService;
public User addUser(UserDto userdto) {
User convertedUser = converterService.convertToEntity(userdto);
convertedUser.setUserId(userdto.getUserId());
convertedUser.setUserName(userdto.getUserName());
User savedUser = userRepository.save(convertedUser);
return savedUser;
}
}
//while debugging userRepository.save(convertedUser) method, it always returning null.
Below is my UserServiceTest.java class
#RunWith(SpringRunner.class)
#SpringBootTest
public class UserServiceTest {
#Autowired
private UserService userService;
#Mock
private ConverterService converterService;
#MockBean
private UserRepository userRepository;
#Test
public void addUserTest() {
UserDto userDto = new UserDto();
userDto.setUserId("123");
userDto.setUserName("AB");
User user = new User("123","ABC");
Mockito.when(converterService.convertToEntity(new UserDto())).thenReturn(user);
Mockito.when(userRepository.save(user)).thenReturn(user);
User user1 = userService.addUser(userDto);
Assert.assertEquals(user,userService.addUser(userDto));
}
}
userService.addUser(userDto) this method is always return by null from service class. because of below condition is failing : Assert.assertEquals(user,userService.addUser(userDto));
you shouldn't pass null value to the assertEquals method. But you can do like this;
Assert.assertEquals(Objects.equals(user,userService.addUser(userDto)),true);
I'm trying to inject JPA repositories managed and implemented by Spring to a service class. But in the runtime program throws NPE on line 39.
Here is my service class:
#Service
public class RegistrationFormProcessor {
private ContractorRepository contractorRepository;
private AddressRepository addressRepository;
private UserRepository userRepository;
private PasswordEncoder passwordEncoder;
#Autowired
public RegistrationFormProcessor(ContractorRepository contractorRepository, AddressRepository addressRepository, UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.contractorRepository = contractorRepository;
this.addressRepository = addressRepository;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public User prepareUser(RegistrationForm registrationForm) {
Address address = new Address(
registrationForm.getCountry(),
registrationForm.getCity(),
registrationForm.getStreet(),
registrationForm.getZipCode(),
registrationForm.getProperty()
);
Address savedAddress = addressRepository.save(address); // line 39
Contractor userCompany = new Contractor(
registrationForm.getCompanyName(),
registrationForm.getShortName(),
savedAddress,
registrationForm.getNip(),
registrationForm.getRegon(),
registrationForm.getPhone(),
registrationForm.getEmail(),
registrationForm.getWebsite()
);
Contractor savedUserCompany = contractorRepository.save(userCompany); // line 51
User user = new User(
registrationForm.getFirstName(),
registrationForm.getLastName(),
registrationForm.getLastName(),
passwordEncoder.encode(registrationForm.getPassword()),
"ROLE_USER",
true,
savedUserCompany
);
User savedUser = userRepository.save(user);
savedUserCompany.setOwner(savedUser);
contractorRepository.save(savedUserCompany);
return savedUser;
}
AddressRepository and other fields are autowired with interfaces that are managed by Spring which is implementing all methods automatically.
import org.springframework.data.repository.CrudRepository;
public interface AddressRepository extends CrudRepository <Address, Long> {}
In my case an instance of RegistrationFormProcessor is an instance managed by Spring (at least I hope it is). RegistrationFormProcessor is annotated #Service and is injected in one of controller class. There it is used and program is throwing mentioned exception. Below a controller:
#Controller
#RequestMapping("/register")
public class RegistrationController {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public RegistrationController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
#GetMapping
public String registerForm(Model model) {
model.addAttribute("registrationForm", new RegistrationForm());
return "register/registration";
}
#PostMapping
public String processRegistrationForm(RegistrationForm form, #Autowired RegistrationFormProcessor registrationFormProcessor) {
registrationFormProcessor.prepareUser(form);
return "redirect:/login";
}
}
If line 39 would be removed program would thow NPE on line 51 (and so on if userRepository.save(user) is called). In these lines should be used repository with methods like save() implemented by Spring-data automatically.
I have similar injections (also in constructors) in controller classes and there everything is working fine.
What I'm doing wrong? Why in service class Spring does not inject these dependencies and fields are null? I tried to set a breakpoint and debug - confirmed that fields are null . As I said above in other classes which are annotated as #Controller same fields also injected in constructor aren't nulls and all is working fine.
Tried to annotate interface AddresRepository as #Repository but it is not working. On the other hand it is not necessary for sure for Spring - Spring is implementing methods correctly as checked in controllers.
Thank you in advance for any suggestions.
You should try add final keyword for your repositories for injecting, like that:
private final ContractorRepository contractorRepository;
private final AddressRepository addressRepository;
...
Problem solved by moving RegistrationFormProcessor injection in controller from injection in controller method as a parameter to declaration as field and injection in controller field.
Before
#Controller
#RequestMapping("/register")
public class RegistrationController {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public RegistrationController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
#PostMapping
public String processRegistrationForm(RegistrationForm form, #Autowired RegistrationFormProcessor registrationFormProcessor) {
registrationFormProcessor.prepareUser(form); // throws NPE - not working
return "redirect:/login";
}
}
After:
#Controller
#RequestMapping("/register")
public class RegistrationController {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final RegistrationFormProcessor registrationFormProcessor;
public RegistrationController(UserRepository userRepository, PasswordEncoder passwordEncoder, RegistrationFormProcessor registrationFormProcessor) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.registrationFormProcessor = registrationFormProcessor;
}
#PostMapping
public String processRegistrationForm(RegistrationForm form) {
registrationFormProcessor.prepareUser(form); //working fine
return "redirect:/login";
}
}
But can't explain why...
Can you remove #Autowired annotation and add final keyword to repository definitions like below:
private final ContractorRepository contractorRepository;
private final AddressRepository addressRepository;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public RegistrationFormProcessor(ContractorRepository contractorRepository, AddressRepository addressRepository, UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.contractorRepository = contractorRepository;
this.addressRepository = addressRepository;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
I'd like to define a my own component by using Spring.
Below there is my code:
MyPasswordEncoderConfig:
#Configuration
#ComponentScan(basePackages = { "my.package" })
public class CryptoConfig {
#Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
MyPasswordEncoder:
#Component
public class MyPasswordEncoder {
#Autowired
private PasswordEncoder passwordEncoder; // Defined in Spring Security.
public String encode(String plainTextPassword) {
return passwordEncoder.encode(plainTextPassword);
}
public boolean matches(String encodedPasswordA, String encodedPasswordB) {
return passwordEncoder.matches(encodedPasswordA, encodedPasswordB);
}
}
MyPasswordEncoderTest:
#ContextConfiguration(classes = {MyPasswordEncoder.class, MyPasswordEncoderConfig.class})
#RunWith(SpringJUnit4ClassRunner.class)
public class MyPasswordEncoderTest {
#Mock
private PasswordEncoder passwordEncoder;
#InjectMocks
#Autowired
private MyPasswordEncoder myPasswordEncoder;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testPasswordMatching() {
String plainTextPassword = "ABCdef123###";
String encodedPassword = myPasswordEncoder.encode(plainTextPassword);
assertTrue(myPasswordEncoder.matches(plainTextPassword, encodedPassword));
}
}
When I run the test, it fails. Checking the result by using the standard output, the passwordEncoder.encode(plainTextPassword); returns a null value.
What am I doing wrong?
UPDATE:
The problem is regarding the PasswordEncoder interface. If I replace it with BCryptPasswordEncoder, the test works fine.
From what I gather from your test code, you actually want to test the functionality of MyPasswordEncoder. So why use mocks?
Why not just use #Autowired for both PasswordEncoder and MyPasswordEncoderlike in the following code:
#ContextConfiguration(classes = {MyPasswordEncoder.class, MyPasswordEncoderConfig.class})
#RunWith(SpringJUnit4ClassRunner.class)
public class MyPasswordEncoderTest {
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private MyPasswordEncoder myPasswordEncoder;
#Test
public void testPasswordMatching() {
String plainTextPassword = "ABCdef123###";
String encodedPassword = myPasswordEncoder.encode(plainTextPassword);
assertTrue(myPasswordEncoder.matches(plainTextPassword, encodedPassword));
}
}
If you do not want to write a spring integration test but a simple unit test and do not want to refactor your code, you could write the following:
public class MyPasswordEncoderTest {
private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
private MyPasswordEncoder myPasswordEncoder = new MyPasswordEncoder();
#Before
public void init() {
ReflectionTestUtils.setField(myPasswordEncoder, "passwordEncoder", passwordEncoder);
}
#Test
public void testPasswordMatching() {
String plainTextPassword = "ABCdef123###";
String encodedPassword = myPasswordEncoder.encode(plainTextPassword);
assertTrue(myPasswordEncoder.matches(plainTextPassword, encodedPassword));
}
}
An even better solution (which ditches the use of Spring ReflectionTestUtils) is to refactor MyPasswordEncoder like so:
#Component
public class MyPasswordEncoder {
private final PasswordEncoder passwordEncoder; // Defined in Spring Security.
#Autowired
public MyPasswordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
public String encode(String plainTextPassword) {
return passwordEncoder.encode(plainTextPassword);
}
public boolean matches(String encodedPasswordA, String encodedPasswordB) {
return passwordEncoder.matches(encodedPasswordA, encodedPasswordB);
}
}
Then the unit test would become:
public class MyPasswordEncoderTest {
private MyPasswordEncoder myPasswordEncoder = new MyPasswordEncoder(new BCryptPasswordEncoder());
#Test
public void testPasswordMatching() {
String plainTextPassword = "ABCdef123###";
String encodedPassword = myPasswordEncoder.encode(plainTextPassword);
assertTrue(myPasswordEncoder.matches(plainTextPassword, encodedPassword));
}
}
The issue here is that because of #InjectMocks, the actual PasswordEncoder that is being injected is the #Mock
#Mock
private PasswordEncoder passwordEncoder;
not the one in your #Configuration class. (Actually, both are injected, but the mock is injected last, so that's the one that is used.) You can verify this with (if the field was visible).
System.out.println(MyPasswordEncoder.passwordEncoder.getClass());
would print something like
class com.spring.PasswordEncoder$$EnhancerByMockitoWithCGLIB$$7d70b580
// pay attention to ^ this part ^
Mocks are typically implemented to return null for reference types, 0 for numerical primitives, and false for boolean.