#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. Круто, большое вам спасибо. Это имеет смысл. Мне нужно уйти сейчас, но я вернусь к этому в течение дня и, надеюсь, смогу это реализовать. Мне нужно будет проверить, почему тестовая конфигурация не вызывалась, а затем изменить реализацию между ней и основной конфигурацией.