Search code examples
javaspringspring-mvcspring-bootmockmvc

Does spring @GetMapping work with MockMvc


HelloController.java

@RestController
class HelloController {

@GetMapping(value = "{id}/hello")
public ModelAndView listAPI(@PathVariable("id") String profileId) {
    ModelAndView mav = new ModelAndView();
    return mav;
 }    
}

HelloControllerTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = HelloConfigTest.class)
class HelloControllerTest {

@Inject
private WebApplicationContext webApplicationContext;

@Inject
private Foo mockFoo

@InjectMocks
HelloController helloController;

private MockMvc mockMvc;

    @Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}

@Test
public void testHello() throws Exception {
    mockMvc.perform(
        get("/{id}/campaigns", "id1"))
        .andExpect(status().isOk()));
  }  
}

    // I have another test that directly calls the controller method. 
// So I need @InjectMocks to get an instance of the controller

@Test
public void test2() {

    when(mockFoo.getX()).thenReturn(true);
    helloController.saveAPI();
}

HelloConfigTest.java

 @Configuration
 @ComponentScan("com.test.controller")
 class HelloConfigTest {

@Bean
public mockFoo() {
    return Mockito.mock(Foo.class); 
  }
}

The response that I get here is 404 and I expect 200.

But it works and I get 200 if I change @GetMapping to @RequestMapping(value="{id}/hello", method=RequestMethod.GET)

Am I missing anything here ?


Solution

  • Your configuration is extremely bare bones

    @Configuration
    @ComponentScan("com.test.controller")
    class HelloConfigTest {
    

    It doesn't register any Spring MVC infrastructure beans, either implicitly or explicitly.

    When MockMvc, internally, creates a TestDispatcherServlet to test your @Controller class, it has to defer to some default Spring MVC infrastructure types.

    Among these infrastructure types is HandlerMapping which is

    to be implemented by objects that define a mapping between requests and handler objects.

    The default implementation used by the TestDispatcherSerlet is DefaultAnnotationHandlerMapping (an old class) which looks for @RequestMapping specifically, it doesn't recursively do a meta-annotation lookup. Your @GetMapping annotated method is therefore not found and not registered as a handler.

    If, instead, you configure your application context with @EnableWebMvc

    @Configuration
    @ComponentScan("com.test.controller")
    @EnableWebMvc
    class HelloConfigTest {
    

    Spring will implicitly register a RequestMappingHandlerMapping, which does do this "recursive" lookup for the annotation hierarchy (called merging). Since @GetMapping is annotated with @RequestMapping, the annotated handler method will be found and registered.


    As for the @InjectMocks, note that the instance referenced by the field is different from the one used to handle the request performed by the MockMvc object. The former is managed by Mockito, the latter by Spring.