Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does @WebMvcTest require @SpringBootApplication annotation?

Tags:

spring-boot

My goal is to migrate a Spring Boot application previously developed with Spring Boot 1.3 to the newest Spring Boot version 1.4. The application consists of several maven modules and only one of them contains class annotated with @SpringBootApplication.

One part of migration is to use @WebMvcTest annotation to efficiently test controllers, and here I get an issue.

Consider an example application from Spring Boot github page. @WebMvcTest annotation works perfectly, because, as far as I understand (after I did several tests), there is a class in the main package annotated with @SpringBootApplication. Note that I follow the same concept as shown in the example above for my own @WebMvcTest tests.

The only difference I see that in my application, controller classes are located in a separate maven module (without @SpringBootApplication annotated class), but with @Configuration and SpringBootConfiguration configurations. If I do not annotate any class with @SpringBootApplication I always get an assertion while testing controller. My assertion is the same as when SampleTestApplication class in the example above modified to have only @EnableAutoConfiguration and @SpringBootConfiguration annotations (@SpringBootApplication is not present):

getVehicleWhenRequestingTextShouldReturnMakeAndModel(sample.test.web.UserVehicleControllerTests)  Time elapsed: 0.013 sec  <<< FAILURE!
java.lang.AssertionError: Status expected:<200> but was:<404>
    at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54)
    at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:81)
    at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:664)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
   at sample.test.web.UserVehicleControllerTests.getVehicleWhenRequestingTextShouldReturnMakeAndModel(UserVehicleControllerTests.java:68)

How should I deal with that? Should I always have class annotated with @SpringBootApplication in order to run @WebMvcTest tests?

EDIT 1: I did a small maven project with 2 modules and a minimal configuration. It is here. Now, I get NoSuchBeanDefinitionException exception for repository defined in another module. If I configure "full" @SpringBootApplication - everything is fine.

EDIT 2: I modified small test project from EDIT 1 to give an original issue. I was playing with different annotations and added @ComponentScan on configuration class, because I suspected that beans are not registered properly. However, I expect that only @Controller bean (defined in @WebMvcTest(...class)) shall be registered based on magic behind @WebMvcTest behaviour.

EDIT 3: Spring Boot project issue.

like image 694
tysonite Avatar asked Aug 11 '16 08:08

tysonite


2 Answers

Short answer: I believe so.

Long answer:

I believe @WebMvcTest needs to find the SpringBootApplication configuration since WebMvcTest's sole purpose is to help simplify tests (SpringBootApplication would rather try to load the whole world).

In your specific case, since you don't have any in your non-test packages, I believe it also finds SampleTestConfiguration which is annotated with @ScanPackages and somehow loads every beans.

Add the following in src/main/java/sample/test

@SpringBootApplication
public class SampleTestConfiguration {

}

And change your test to this:

@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class MyControllerTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private MyService ms;

    @Autowired
    private ApplicationContext context;

    @Test
    public void getDataAndExpectOkStatus() throws Exception {
        given(ms.execute("1")).willReturn(false);
        mvc.perform(get("/1/data").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()).andExpect(content().string("false"));
    }

    @Test
    public void testMyControllerInAppCtx() {
        assertThat(context.getBean(MyController.class), is(not(nullValue())));
    }

    @Test
    public void testNoMyAnotherControllerInAppCtx() {
        try {
            context.getBean(MyAnotherController.class);
            fail("Bean exists");
        } catch (BeansException e) {
            // ok
        }
    }
}

@WebMvcTest finds the SpringBootApplication, then load only a limited number of beans (see documentation):

@WebMvcTest will auto-configure the Spring MVC infrastructure and limit scanned beans to @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver. Regular @Component beans will not be scanned when using this annotation.

WebMvcTest requires SpringBootApplication: WebMvcTest inherits many AutoConfiguration, so it needs SpringBoot to load them. Then it disables many other AutoConfiguration and your Controllers become easily testable.

The whole point of using WebMvcTest is when you have a SpringBootApplication and you wish to make it simpler to test by disabling all beans except Controllers. If you don't have SpringBootApplication, then why use WebMvcTest at all?

like image 78
alexbt Avatar answered Nov 26 '22 23:11

alexbt


It's an old topic, but there is a solution which wasn't mentioned here.

You can create a class annotated with SpringBootApplication just in your test sources. Then, you still have a nice, multi-module structure of your project, with just one "real" SpringBootApplication.

like image 20
mat3e Avatar answered Nov 26 '22 22:11

mat3e