Home > Net >  Problem with password encoder in the test
Problem with password encoder in the test

Time:10-26

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.

  • Related