Search code examples
javaspring-mvc

@EnableAutoConfiguration doesn't work with @WebMvcTest. Why?


I discovered that I have to manually @Import autoconfiguration classes when using @WebMvcTest:

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(controllers = MyWebMvcTest.ComplimentingConfig.class)
@Import({GatewayServerMvcAutoConfiguration.class,
        SslAutoConfiguration.class,
        RestClientAutoConfiguration.class})
public class MyWebMvcTest {
    @Autowired
    MockMvc mockMvc;

    @Test
    void test() throws Exception {
        mockMvc.perform(get("/get"))
                .andExpect(status().isOk());
    }

    @Configuration
    static class ComplimentingConfig {
        @Bean
        public RouterFunction<ServerResponse> getRoute() {
            return route()
                    .GET("/get", http("https://httpbin.org/status/200"))
                    .build();
        }
    }
}

If I decide to import an @EnableAutoConfiguration-annotated configuration and thus include those imports implicitly, I get an exception as some required beans are not present in the context

@WebMvcTest(controllers = MyWebMvcTest.ComplimentingConfig.class)
@Import(MyWebMvcTest.ComplimentingConfig.class)
public class MyWebMvcTest {

// ...

    @Configuration
    @EnableAutoConfiguration
    static class ComplimentingConfig {
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.gateway.server.mvc.handler.ProxyExchangeHandlerFunction' available

Why doesn't @EnableAutoConfiguration work with @WebMvcTest?

Java 17, Spring MVC 6.1.5, Spring Boot 3.2.4

Note. I used Spring Cloud Gateway MVC in the MRE above to necessitate additional @Imports. Had I used a plain RouterFuction (e.g. route().GET("/get", r -> ServerResponse.ok().build()).build()), the whole point would've been moot since all required beans would be implicitly provided by @WebMvcTest


Solution

  • First you need to understand between slice testing and integration testing.

    @WebMvcTest does not start full Spring application context. It is used for slice testing of web layer only. It scans for classes annotated with @Controller. To narrow down you can mention controller name like @WebMvcTest(XyzController.class). It is often accompanied with @MockBean in case you controller has reference to another class. It will not read your other configurations. Similarly, @DataJpaTest loads @Repository classes.

    Now you left with two options:

    1. @SpringBootTest - will look for main configuration class (one you annotated with @SpringBootApplication) and start Spring application context. This you can use for your integration testing. Here you defined either you want to use server or not

      @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
      class TestClass {}
      

      This will also start you server. If you don't want that you can use

      @SpringBootTest
      @AutoConfigureMockMvc
      class TestClass {}
      
    2. @ContextConfiguration(classes = {XyzConfig.class}) - will load only mentioned configurations. This will be your slice testing.

      @ExtendWith(SpringExtension.class)
      @ContextConfiguration(classes = {XyzConfig.class})
      class TestClass {}
      

    Now you can decide how you want to proceed.

    Why doesn't @EnableAutoConfiguration work with @WebMvcTest ?

    @WebMvcTest annotation will disable full auto-configuration and apply only configuration relevant to MVC tests, like I explained above. Even if you have explicitly mentioned auto-configuration, @WebMvcTest is overriding it. Read this