tysonite
tysonite

Reputation: 477

Does @WebMvcTest require @SpringBootApplication annotation?

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.

Upvotes: 13

Views: 9051

Answers (4)

Alexey Bril
Alexey Bril

Reputation: 543

There is one more solution. You can not use @WebMvcTest, but configure MockMvc yourself through the builder


class TestControllerTest {

    private MockMvc mvc;

    @BeforeEach
    public void setup() {
        mvc = MockMvcBuilders.standaloneSetup(new TestController())
                .build();
    }

    @Test
    void test() throws Exception {

        // When
        var res = mvc.perform(MockMvcRequestBuilders.get("/test/test"));

        // Then
        res.andExpect(status().isOk());
    }
}     

But this solution may entail a number of other problems, such as problems with configurations, environment property injections, etc.

Upvotes: 0

alexbt
alexbt

Reputation: 17045

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?

Upvotes: 6

john ding
john ding

Reputation: 397

Yes,according to the spring boot docs

The search algorithm works up from the package that contains the test until it finds a @SpringBootApplication or @SpringBootConfiguration annotated class. As long as you’ve structure your code in a sensible way your main configuration is usually found.

But after I started using @WebMvcTest,spring boot still try to load other beans, finally TypeExcludeFilter did the trick.

    @RunWith(SpringRunner.class)
    @WebMvcTest(controllers = {JzYsController.class} )
    public class JzYsControllerTest {

        private static final String REST_V4_JZYS = "/rest/v4/JzYs";

        @Autowired
        private MockMvc mockMvc;

        @MockBean
        private JzYsService service;

        @Test
        public void deleteYsByMlbh() throws Exception {
            Mockito.when(service.deleteYsByMlbh(Mockito.anyString())).thenReturn(Optional.of(1));
            mockMvc.perform(delete(REST_V4_JZYS + "?mbbh=861FA4B0E40F5C7FECAF09C150BF3B01"))
            .andExpect(status().isNoContent());
        }

        @SpringBootConfiguration
        @ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
        public static class config{
        }
    }

Upvotes: 2

mat3e
mat3e

Reputation: 779

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.

Upvotes: 2

Related Questions