Как игнорировать аннотированный класс @EnableWebSecurity в тестах @WebMvcTest - PullRequest
1 голос
/ 12 января 2020

В следующем тестовом классе я не хочу, чтобы аннотированный класс @EnableWebSecurity был перехвачен контекстом Spring:

@WebMvcTest(controllers = UserController.class)
class UserControllerTest {

    @MockBean
    private UserService userService;

    @Autowired
    private ObjectMapper jsonMapper;

    @Autowired
    private MockMvc mockMvc;

    @Test
    void create_should_return_registered_user_when_request_is_valid() throws Exception {
        // given
        final String EMAIL = "test@test.com";
        final String PASSWORD = "test_password";
        final UserDto userDto = buildDto(EMAIL, PASSWORD);
        final User expectedUser = buildUser(EMAIL, PASSWORD);

        // when
        when(userService.registerUser(userDto)).thenReturn(expectedUser);

        // then
        MvcResult response = mockMvc.perform(post(UserAPI.BASE_URL)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonMapper.writeValueAsString(userDto)))
                .andExpect(status().isCreated())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andReturn();

        String responseBodyJson = response.getResponse().getContentAsString();
        User responseUser = jsonMapper.readValue(responseBodyJson, User.class);

        assertThat(responseUser, is(equalTo(expectedUser)));
        verify(userService, times(1)).registerUser(userDto);
        verifyNoMoreInteractions(userService);
    }

    @Test
    void create_should_return_conflict_when_request_valid_but_email_in_use() throws Exception {
        // given
        final String EMAIL = "test@test.com";
        final String PASSWORD = "test_password";
        final UserDto userDto = buildDto(EMAIL, PASSWORD);

        // when
        when(userService.registerUser(userDto)).thenThrow(new EmailAlreadyInUseException(EMAIL));

        // then
        mockMvc.perform(post(UserAPI.BASE_URL)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonMapper.writeValueAsString(userDto)))
                .andExpect(status().isConflict());

        verify(userService, times(1)).registerUser(userDto);
        verifyNoMoreInteractions(userService);
    }

    @Test
    void create_should_return_bad_request_when_request_has_invalid_email() throws Exception {
        // given
        final String BAD_EMAIL = "test_test.com";
        final String PASSWORD = "test_password";
        final UserDto userDto = buildDto(BAD_EMAIL, PASSWORD);

        // when

        // then
        mockMvc.perform(post(UserAPI.BASE_URL)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonMapper.writeValueAsString(userDto)))
                .andExpect(status().isBadRequest());

        verifyNoInteractions(userService);
    }

    @Test
    void create_should_return_bad_request_when_request_has_invalid_password() throws Exception {
        // given
        final String EMAIL = "test@test.com";
        final String BAD_PASSWORD = "";
        final UserDto userDto = buildDto(EMAIL, BAD_PASSWORD);

        // when

        // then
        mockMvc.perform(post(UserAPI.BASE_URL)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonMapper.writeValueAsString(userDto)))
                .andExpect(status().isBadRequest());

        verifyNoInteractions(userService);
    }

    @Test
    void create_should_return_bad_request_when_request_is_missing_email() throws Exception {
        // given
        final String PASSWORD = "test_password";
        final UserDto userDto = buildDto(null, PASSWORD);

        // when

        // then
        mockMvc.perform(post(UserAPI.BASE_URL)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonMapper.writeValueAsString(userDto)))
                .andExpect(status().isBadRequest());

        verifyNoInteractions(userService);
    }

    @Test
    void create_should_return_bad_request_when_request_is_missing_password() throws Exception {
        // given
        final String EMAIL = "test@test.com";
        final UserDto userDto = buildDto(EMAIL, null);

        // when

        // then
        mockMvc.perform(post(UserAPI.BASE_URL)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonMapper.writeValueAsString(userDto)))
                .andExpect(status().isBadRequest());

        verifyNoInteractions(userService);
    }

    private UserDto buildDto(String email, String password) {
        UserDto userDto = new UserDto();
        userDto.setEmail(email);
        userDto.setPassword(password);
        return userDto;
    }

    private User buildUser(String email, String password){
        User user = new User();
        user.setId(1);
        user.setEmail(email);
        user.setPassword(password);
        return user;
    }

}

Сейчас он загружается по умолчанию и, поскольку его зависимости не загружается, выдает ошибку:

Параметру 0 конструктора в com.example.ordersapi.auth.configuration.SecurityConfiguration требуется bean-компонент типа 'org.springframework.security.core.userdetails.UserDetailsService 'это не может быть найдено.

Я видел некоторые решения, такие как @WebMvcTest(controllers = SomeController.class, secure = false), но они устарели.

Я использую Spring Boot v2.2.2. RELEASE.


Вот класс конфигурации безопасности:

@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Value("${spring.h2.console.enabled:false}")
    private boolean h2ConsoleEnabled;

    private final UserDetailsService userDetailsService;
    private final AuthorizationFilter authorizationFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        if (h2ConsoleEnabled) {
            http.authorizeRequests()
                    .antMatchers("/h2-console", "/h2-console/**").permitAll()
                    .and()
                    .headers().frameOptions().sameOrigin();
        }

        http.cors().and().csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint(unauthorizedHandler())
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, AuthenticationAPI.BASE_URL).permitAll()
                .anyRequest().authenticated();

        http.addFilterBefore(authorizationFilter, UsernamePasswordAuthenticationFilter.class);
    }

    private AuthenticationEntryPoint unauthorizedHandler() {
        return (request, response, e) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }

    /**
     * We have to create this bean otherwise we can't wire AuthenticationManager in our code.
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

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

}

Ответы [ 3 ]

1 голос
/ 06 февраля 2020

Конфигурация безопасности может быть переопределена с помощью атрибутов @Configuration и @EnableWebSecurity. Поскольку вы не используете @TestConfiguration, вам, вероятно, потребуется импортировать класс с помощью @Import, как показано ниже. Мне нравится это решение, а не сканирование вашего хост-пакета на предмет bean-компонентов, потому что я чувствую, что вы лучше контролируете загрузку фреймворка.

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = MyController.class)
@Import(MyController.class)
public class MyControlleTests {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private SomeDependency someDependencyNeeded;

    @Configuration
    @EnableWebSecurity
    static class SecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception
        {
            http
                    .csrf().disable()
                    .authorizeRequests().anyRequest().anonymous();
        }
    }

    @Test
    public void some_route_returns_ok() throws Exception {

        MockHttpServletRequestBuilder requestBuilder =
                MockMvcRequestBuilders.get("mycontrolleraction");

        mvc
                .perform(requestBuilder)
                .andExpect(MockMvcResultMatchers.status().isOk());

    }
}

Обратите внимание, что можно утверждать, что вы должны просто включить безопасность в свой пакет. тестовое задание; тем не менее, я считаю, что вы должны тестировать каждый компонент вашей архитектуры в максимально возможной изоляции.

0 голосов
/ 21 января 2020

Я также получил ту же проблему, то есть код 401, после миграции Spring Boot с 2.1.x на 2.2.x. С тех пор поле secure было удалено из аннотации @WebMvcTest.

Я исправил, добавив эту аннотацию, которая игнорирует фильтры, включая фильтр аутентификации:

@WebMvcTest(value = SomeResource.class)
@AutoConfigureMockMvc(addFilters = false)
class SomeTest  {
}
0 голосов
/ 12 января 2020

Самое простое решение, которое я нашел, - добавить в свой класс SecurityConfiguration @Profile(!test). Это должно полностью предотвратить загрузку класса во время тестов. По умолчанию тесты запускаются с тестовым профилем, если вы переопределяете то, что вам, возможно, придется добавить в используемый вами профиль. (Журналы показывают, какой профиль активен при запуске контекста). Подробнее о профилях: https://www.baeldung.com/spring-profiles.

Вы также можете использовать @WithMockUser(roles = "MANAGER"). См. Этот вопрос для получения дополнительной информации: Spring Test & Security: Как имитировать аутентификацию?

...