Issue with testing Spring MVC slice in SpringBoot 1.4

Who is interested in loading the full application should try using @SpringBootTest combined with @AutoConfigureMockMvc rather than the @WebMvcTest.

I have been struggling with the problem for quite a while, but finally I got the complete picture.
The many tutorials on the internet, as well as the official Spring documentation I found so far , state that you can test your controllers using @WebMvcTest; that's entirely correct, still omitting half of the story though.
As pointed out by the javadoc of such annotation, @WebMvcTest is only intended to test your controllers, and won't load all your app's beans at all, and this is by design.
It is even incompatible with explicit bean scanning annotations like @Componentscan.

I suggest anybody interested in the matter, to read the full javadoc of the annotation (which is just 30 lines long and stuffed of condensed useful info) but I'll extract a couple of gems relevant to my situation.

from Annotation Type WebMvcTest

Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. @Controller, @ControllerAdvice, @JsonComponent Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not @Component, @Service or @Repository beans). [...] If you are looking to load your full application configuration and use MockMVC, you should consider @SpringBootTest combined with @AutoConfigureMockMvc rather than this annotation.

And actually, only @SpringBootTest + @AutoConfigureMockMvc fixed my problem, all other approaches that made use of @WebMvcTest failed to load some of the required beans.

EDIT

I take back my comment I made about Spring documentation, because I wasn't aware that a slice was implied when one uses a @WebMvcTest; actually the MVC slice documentation put it clear that not all the app is loaded, which is by the very nature of a slice.

Custom test slice with Spring Boot 1.4

Test slicing is about segmenting the ApplicationContext that is created for your test. Typically, if you want to test a controller using MockMvc, surely you don’t want to bother with the data layer. Instead you’d probably want to mock the service that your controller uses and validate that all the web-related interaction works as expected.


You are using @WebMvcTest while also manually configuring a MockMvc instance. That doesn't make sense as one of the main purposes of @WebMvcTest is to automatically configure a MockMvc instance for you. Furthermore, in your manual configuration you're using standaloneSetup which means that you need to fully configure the controller that's being tested, including injecting any dependencies into it. You're not doing that which causes the NullPointerException.

If you want to use @WebMvcTest, and I would recommend that you do, you can remove your setUp method entirely and have an auto-configured MockMvc instance injected instead using an @Autowired field.

Then, to control the ProductService that's used by ProductController, you can use the new @MockBean annotation to create a mock ProductService that will then be injected into ProductController.

These changes leave your test class looking like this:

package guru.springframework.controllers;

import guru.springframework.services.ProductService;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@WebMvcTest(ProductController.class)
public class ProductControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private ProductService productService;

    @Test
    public void testList() throws Exception {
      mockMvc.perform(MockMvcRequestBuilders.get("/products"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                 .andExpect(MockMvcResultMatchers.view().name("products"))
                 .andExpect(MockMvcResultMatchers.model().attributeExists("products"))
               .andExpect(MockMvcResultMatchers.model().attribute("products",
                        Matchers.is(Matchers.empty())));

    }
}