Как подделать кодировщик паролей с помощью Mockito

#java #unit-testing #spring-security #mockito

#java #модульное тестирование #spring-безопасность #mockito

Вопрос:

Я использую Spring security и хотел бы модульно протестировать пароль, возвращаемый моей службой. Однако, поскольку это зашифровано, я пытаюсь издеваться над методом, который выполняет это кодирование, которое находится внутри класса, который расширяется от WebSecurityConfigurerAdapter .

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

Итак, я пытаюсь сделать что-то вроде этого

 @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", "user@gmail.com", "user@gmail.com", "123456", "basic");
    User userMocked = new User(userId, "user", "test", "user@gmail.com", "user@gmail.com", "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());
}
  
 

Но это не удается с DaoAuthenticationConfigurer cannot be returned by generateToken() generateToken() should return String

Не уверен, что подход правильный, и если это то, что я могу делать неправильно.

Спасибо.

Обновить

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

Основываясь на моем разговоре с Plalx, кажется, что я должен вводить кодировщик паролей, а не bycript, и иметь разные конфигурации между основным и тестовым классами. Попробую это и вернусь сюда с дальнейшими обновлениями.

Комментарии:

1. Не видя реализации createUser , невозможно помочь, но ваш when(...) не выглядит так, как будто он должен работать.

Ответ №1:

Теперь это работает. Вместо того, чтобы издеваться над хешированием, наличие двух разных компонентов, одного под Configuration , а другого под TestConfiguration , сделало свое дело. Компонент для тестов не имеет хеширования.

Конфигурация для основного приложения:

 @Configuration
public class WebConfig {

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

Конфигурация для тестов:

 @TestConfiguration
public class TestConfig {

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

Тест:

 @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", "user@gmail.com", "123456", "user@gmail.com", "basic");
    UserCreateDto userCreateDto = new UserCreateDto("user", "test", "user@gmail.com", "123456", user@gmail.com", "basic");

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

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

}
 

PS: Не забудьте @Import класс TestConfig над именем класса.

И, наконец, класс Service

 @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());
}
}
 

Ответ №2:

Если вы имеете в виду интеграционный тест… Я не могу это попробовать, но я думаю, вы могли бы настроить PasswordEncoder как a @Bean , а затем переопределить конфигурацию компонента для использования некодирования PasswordEncoder .

например

 @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);        
    }
}
 

И затем в вашем тесте у вас будет определение компонента, такое как:

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

Могут быть некоторые настройки, но это идея.


Обратите внимание, что если единственной причиной использования configure является переопределение соавторов, то вы можете просто удалить configure блок и вместо этого определить PasswordEncoder amp; UserDetailsService beans .

Комментарии:

1. Привет, спасибо за ваш ответ. Что бы изменилось в этой строке? when(auth.userDetailsService(myUserDetailsService).passwordEncoder(NoOpPasswordEncoder.getInstance())).thenReturn(auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoderTest));

2. И я получаю эту ошибку java.lang.IllegalStateException: Test classes cannot include @Bean methods , когда добавляю компонент в тестовый класс.

3. Возможно, вам придется определить компонент для другого класса для использования @SpringBootTest(classes = { TestConfig.class }) . Я думаю, что затем насмешливая строка (при вызове) может быть полностью удалена, поскольку вы будете использовать стандарт UserDetailsService , который будет настроен с помощью подделки TestPasswordEncoder .

4. Спасибо. Я создал класс для компонента и удалил строку when, но теперь я получаю Invalid bean definition with name 'passwordEncoder' .

5. Круто, большое вам спасибо. Это имеет смысл. Мне нужно уйти сейчас, но я вернусь к этому в течение дня и, надеюсь, смогу это реализовать. Мне нужно будет проверить, почему тестовая конфигурация не вызывалась, а затем изменить реализацию между ней и основной конфигурацией.