0

I have pretty simple UserCreator service.

@Service
public class UserCreator {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public UserCreator(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    public CreateUserResult createUser(CreateUserCommand command) {
        validate(command);
        return new CreateUserResult(doCreateUser(command).getId());
    }

    private User doCreateUser(CreateUserCommand command) {
        final var user = User.builder()
                .password(passwordEncoder.encode(command.getPassword()))
                .email(command.getEmail())
                .birthDate(command.getBirthDate())
                .build();
        return userRepository.save(user);
    }
}

And I'm adding few unit tests, one of those tests validates if created user has encoded password.

@ExtendWith(MockitoExtension.class)
public class UserCreatorTest {

    @Mock
    private UserRepository userRepository;
    private UserCreator userCreator;
    private PasswordEncoder passwordEncoder;

    @BeforeEach
    public void setUp() {
        this.passwordEncoder = new BCryptPasswordEncoder();
        this.userCreator = new UserCreator(userRepository, passwordEncoder);
    }

    @Test
    @DisplayName("Created user has encoded password")
    void test2() {
        final var userCaptor = ArgumentCaptor.forClass(User.class);
        when(userRepository.save(any())).thenReturn(User.builder().build());
        final var command = CreateUserCommand.builder()
                .email("[email protected]")
                .password("test123")
                .birthDate(LocalDate.of(1990, 10, 10))
                .build();
        final var expectedPassword = passwordEncoder.encode(command.getPassword());

        userCreator.createUser(command);

        verify(userRepository, times(1)).save(userCaptor.capture());
        assertTrue(passwordEncoder.matches(expectedPassword, userCaptor.getValue().getPassword()));
    }
}

However, this test fails on assertTrue(), those passwords does not match.

enter image description here

Why is that?

2
  • BCryptPasswordEncoder#encode isn't deterministic. It'll include a random salt, so your expectedPassword and the passwordEncoder.encode in doCreateUser won't necessarily match. Use a mock PasswordEncoder, or (maybe not worth it) pass in a mock SecureRandom so both calls match. Commented Nov 1, 2020 at 10:05
  • About calling new BCryptPasswordEncoder() - I'm indeed calling that, but as you can see, same isntance is passed to service and used in unit test. I'm not using two different instances here. Commented Nov 1, 2020 at 10:23

1 Answer 1

1

I checked the other part of my application, when I'm handling authentication using BCryptPasswordEncoder and it actually solved issue for me.

Instead of doing

final var expectedPassword = passwordEncoder.encode(command.getPassword());
assertTrue(passwordEncoder.matches(expectedPassword, userCaptor.getValue().getPassword()));

I should just do

assertTrue(passwordEncoder.matches(command.getPassword(), userCaptor.getValue().getPassword()));

Now my test is passing.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.