Spring Boot WebMvc Tests not including Spring Security

跟風遠走 提交于 2019-12-24 10:47:26

问题


I am developing a Spring Boot MVC application which has a certain number of controllers. My root controller is:

@Controller
@RequestMapping("/")
public class RootController {

    @GetMapping
    public String showStartPage() {
        log.info("GET: Show home page");
        return "index";
    }
}

I have successfully implemented MVC tests for the controllers. The test for my RootController is:

@RunWith(SpringRunner.class)
@WebMvcTest(RootController.class)
public class RootControllerMvcTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void testRoot() throws Exception {
        mvc.perform(get("/").accept(MediaType.TEXT_HTML))
            .andExpect(status().isOk())
            .andExpect(view().name("index"));
    }
}

Problem:

But, when I introduced Spring Security authentication and authorization, all the mvc controller tests broke down. Assertion error for the root controller test is:

java.lang.AssertionError: Status 
Expected :200
Actual   :401

My security configuration is:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/", "/fonts/*").permitAll()
            .antMatchers("/user/**").hasAuthority("ADMIN")
            .anyRequest().fullyAuthenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .failureUrl("/login?error")
            .usernameParameter("email")
            .permitAll()
            .and()
            .logout()
            .logoutUrl("/logout")
            .deleteCookies("remember-me")
            .logoutSuccessUrl("/")
            .permitAll()
            .and()
            .rememberMe();
    }

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

Solution:

Then, I managed to solve the problem with:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class RootControllerMvcTest {
...
}

In this case, my tests load the entire Application context.

My questions are:

  1. How is it possible to keep my mvc tests separated from authentication and authorization process and test only the logic of the controllers?
  2. What is the best practice to test the authentication and authorization implementation? Do I have to use @SpringBootTest for this purpose?
  3. Is it a good decision to test my controllers and security logic separately?

Thank you.


回答1:


The methods proposed before seem to be deprecated in newer versions of spring.

On the other hand, the same functionality now can be used as additional arguments.

For example, in my case with WebMvcTest I moved from

@RunWith(SpringRunner.class)
@WebMvcTest(ImportController.class)

to

@RunWith(SpringRunner.class)
@WebMvcTest(value = ImportController.class, secure = false)

and all worked just fine.




回答2:


A lot of this might be opinion but I'll give my two cents. First the goal should be coverage and time to market. That is I need a solution that is reasonably fast to implement and produces software that will work in production. So I am not concerned with ideology too much.

To that end I like to use a mixture of unit test and integration tests. Where unit tests try to achieve close to 100% code coverage, integration focuses more on the interactions with other systems, e.g. DB's, clients, other services, etc. BTW, both unit and integration tests must be automated, so integrations should be done without a human being present.

There are two ways I have used in the past to unit test my controllers. One is to call the controllers methods directly, outside of a Spring context. We were able to hit our code coverage goals. This worked for a while but then failed when we started using Spring's HATEOS features, which relies on using the Spring MVC context to create the links that HATEOS is based on. So we switched to MockMvc for our unit tests. This worked and we hit our coverage goals. Now in your case the problem is the security is getting in the way of your unit tests. I would look to side step the security configuration so that I can hit my coverage goals, security can be tested in integrations tests. This can be done by using the @Profile annotation on your SecurityConfig class. In our microservices network we use @Profile to enable and disable configuration classes. For example, we only register to our Eureka servers if there is a certain profile being used. There are different ways you can do this. One way is to follow what we did for our Eureka configuration, its only enabled if a profile that we call active-eureka is present, we made this name up. This prevents the Eureka servers being contacted when we run our microservices from our IDE's. In your case you can have a profile called activate-security. This would allow you to do your MockMvc based unit testing with security spoiling the fun.

Now with unit tests out of the way, you still must conduct integration tests against your services with security enabled. There are different approaches to this. One approach that we use is to use the @SpringBootTest annotation with the TestRestTemplate class. This starts up our microservice and then we make real service calls to it using the TestRestTemplate. TestRestTemplate is a real http RestTemplate client, except that it knows what port your web service is running on, so you can remove some boiler plate code on trying to figure out the port. With TestRestTamplte you can leave out the http://host:port portion of the URL. It is described here. Please note that if we had security enabled, like you do, then we would need to go through that security as if we were a real client in production, meaning we do not side step the security but actively engage it, this is the opposite of what we did during unit-testing.

We have different goals for our unit tests and integration tests. Using the approach outlined above we can properly concentrate on those goals independently.




回答3:


Answer 1:


I have excluded Spring Security features from my MVC tests as follows:
  1. Added src/test/java/resources/application-disabled-security.yml:

    security: basic: enabled: false

  2. Added @ActiveProfiles("disabled-security") annotation to the mvc tests.



来源:https://stackoverflow.com/questions/42327923/spring-boot-webmvc-tests-not-including-spring-security

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!