Home > Enterprise >  How to mock password encoder using Mockito
How to mock password encoder using Mockito

Time:12-10

I'm using Spring security and would like to unit test the password returned by my service. However, as this is encrypted, I'm trying to mock the method that does this encription which is inside the class which extends from WebSecurityConfigurerAdapter.

@Override
public void configure(AuthenticationManagerBuilder auth){
     auth.userDetailsService(userDetailsService).passwordEncoder(NoOpPasswordEncoder.getInstance());        
}

So I'm trying something like this

@Autowired
AuthenticationManagerBuilder auth;


static class PasswordEncoderTest implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return charSequence.toString().equals(s);
    }
}

@Test
void testCreateUser() throws Exception {

    UserCreateDto userCreateDto = new UserCreateDto("user", "test", "[email protected]", "[email protected]", "123456", "basic");
    User userMocked = new User(userId, "user", "test", "[email protected]", "[email protected]", "123456", "basic");

    PasswordEncoderTest passwordEncoderTest = new PasswordEncoderTest();
    passwordEncoderTest.encode("123456");
    when(auth.userDetailsService(myUserDetailsService).passwordEncoder(NoOpPasswordEncoder.getInstance())).thenReturn(auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoderTest));

    userCreateDto = userCommandService.createUser(userCreateDto);

    assertEquals(userMocked.getPassword(), userCreateDto.getPassword());
}
  

But it fails with DaoAuthenticationConfigurer cannot be returned by generateToken() generateToken() should return String

Not sure if the approach is correct and if it is what I may be doing wrong.

Thank you.

UPDATE

https://github.com/francislainy/adverts-backend/tree/dev_jwt

Based on my conversation with Plalx, It seems I should be injecting password encoder rather than bycript and have different configurations between the main and test classes. Will try that and get back here with further updates.

CodePudding user response:

It's working now. Rather than mocking the hashing, having two different beans, one under Configuration and the other under TestConfiguration did the trick. The bean for the tests doesn't not have hashing.

Config for the main application:

@Configuration
public class WebConfig {

    @Bean public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Config for the tests:

@TestConfiguration
public class TestConfig {

    @Bean public PasswordEncoder passwordEncoder() {
       return NoOpPasswordEncoder.getInstance() ;
    }
}

Test:

@Import({TestConfig.class})
@WebMvcTest(UserCommandService.class)
class UserCommandServiceTest {

@MockBean
UserRepository userRepository;

@Autowired
private UserCommandService userCommandService;

@MockBean
private MyUserDetailsService myUserDetailsService;

@MockBean
private JwtUtil jwtUtil;

@Test
void testCreateUser() {

    User userMocked = new User(userId, "user", "test", "[email protected]", "123456", "[email protected]", "basic");
    UserCreateDto userCreateDto = new UserCreateDto("user", "test", "[email protected]", "123456", [email protected]", "basic");

    when(userRepository.save(any(User.class))).thenReturn(userMocked);

    userCreateDto = userCommandService.createUser(userCreateDto);
    assertEquals(userMocked.getPassword(), userCreateDto.getPassword());
}

}

PS: Don't forget to @Import the TestConfig class above the class name.

And finally the Service class

@Service
public class UserCommandServiceImpl implements UserCommandService {

private final UserRepository userRepository;

@Autowired
private PasswordEncoder passwordEncoder;

public UserCommandServiceImpl(UserRepository userRepository) {
    this.userRepository = userRepository;
}

@Override
public UserCreateDto createUser(UserCreateDto userCreateDto) {
    User user = new User();
    user.setFirstname(userCreateDto.getFirstname());
    user.setLastname(userCreateDto.getLastname());
    user.setEmail(userCreateDto.getEmail());
    user.setUsername(userCreateDto.getUsername());
    user.setPassword(userCreateDto.getPassword());

    user = userRepository.save(user);

    return new UserCreateDto(user.getId(), user.getFirstname(), user.getLastname(), user.getUsername(), passwordEncoder.encode(user.getPassword()), user.getEmail(), user.getRole());
}
}

CodePudding user response:

If you mean an integration test... I can't really try it, but I think you could configure the PasswordEncoder as a @Bean and then override the bean configuration to use a non-encoding PasswordEncoder.

e.g.

@Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired private PasswordEncoder passwordEncoder;

    @Bean public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth){
     
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder);        
    }
}

And then in your test you'd have a bean definition such as:

@Bean public PasswordEncoder passwordEncoder() {
    return new PasswordEncoderTest();
}

There might be some tweaking, but that's the idea.


Note that if the only reason for using configure is to override the collaborators then you can just remove the configure block and rather define the PasswordEncoder & UserDetailsService beans.

  • Related