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.
Why is that?

BCryptPasswordEncoder#encodeisn't deterministic. It'll include a random salt, so yourexpectedPasswordand thepasswordEncoder.encodeindoCreateUserwon't necessarily match. Use a mockPasswordEncoder, or (maybe not worth it) pass in a mockSecureRandomso both calls match.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.