Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ignore @EnableWebSecurity annotated class in @WebMvcTest tests

In the following test class, I don't want the @EnableWebSecurity annotated class to be caught by the Spring Context:

@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 = "[email protected]";
        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 = "[email protected]";
        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 = "[email protected]";
        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 = "[email protected]";
        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;
    }

}

Right now it is being loaded by default and, because its dependencies are not loaded, it throws the error:

Parameter 0 of constructor in com.example.ordersapi.auth.configuration.SecurityConfiguration required a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' that could not be found.

I've seen some solutions such as @WebMvcTest(controllers = SomeController.class, secure = false) but these seem to be deprecated.

I'm running Spring Boot v2.2.2.RELEASE.


Here's the Security Configuration class:

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

}
like image 928
João Dias Avatar asked Oct 23 '25 17:10

João Dias


2 Answers

The easiest solution I’ve found is to, on your SecurityConfiguration class add @Profile(!test). This should completely prevent the class from being loaded during tests. By default tests run with the test profile, if you’re overriding that you might have to put in the profile you’re using. (The logs show which profile is active when the context starts). More about profiles: https://www.baeldung.com/spring-profiles.

You can also use @WithMockUser(roles = "MANAGER"). See this question for more info: Spring Test & Security: How to mock authentication?

like image 92
Pseudos Avatar answered Oct 26 '25 07:10

Pseudos


I also got the same issue, that is 401 code, after migrating Spring Boot from 2.1.x to 2.2.x. Since then, the secure field was removed from @WebMvcTest annotation.

I fixed by adding this annotation that ignore filters including authentication filter:

@WebMvcTest(value = SomeResource.class)
@AutoConfigureMockMvc(addFilters = false)
class SomeTest  {
}
like image 30
nordeen78 Avatar answered Oct 26 '25 07:10

nordeen78