问题
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:
- How is it possible to keep my mvc tests separated from authentication and authorization process and test only the logic of the controllers?
- What is the best practice to test the authentication and authorization implementation? Do I have to use @SpringBootTest for this purpose?
- 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:
Added
src/test/java/resources/application-disabled-security.yml
:security: basic: enabled: false
Added
@ActiveProfiles("disabled-security")
annotation to the mvc tests.
来源:https://stackoverflow.com/questions/42327923/spring-boot-webmvc-tests-not-including-spring-security