spring-bootunit-testingspring-mvcconstructor-injectionspring-mvc-test

Spring Boot @WebMvcTest with constructor injection not working


Constructor injection with @WebMvcTest is NOT working. The mocked bean SomeService is not initialized. Why? Doesn't Mockito create SomeService independently of Spring Boot?

If I use @MockBean everything is ok but I'd like to make use of constructor injection.

Any ideas?

@WebMvcTest with constructor injection not working

package com.ust.webmini;

@RequiredArgsConstructor
@RestController
public class HelpController {
    @NonNull
    private final SomeService someService;

    @GetMapping("help")
    public String help() {
        return this.someService.getTip();        
    }
}

-------------------------------------------
package com.ust.webmini;

@Service
public class SomeService {
    public String getTip() {
        return "You'd better learn Spring!";
    }
}
-------------------------------------------

@WebMvcTest(HelpController.class)
public class WebMockTest {

    @Autowired
    private MockMvc mockMvc;
    
/* if we use this instead of the 2 lines below, the test will work!
    @MockBean 
    private SomeService someService;
*/
    private SomeService someService = Mockito.mock(SomeService.class);
    private HelpController adviceController = new HelpController(someService);

    @Test
    public void test() {
         // do stuff        
    }
}
---------------------------------------
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.ust.webmini.HelpController required a bean of type 'com.ust.webmini.SomeService' that could not be found.


Action:

Consider defining a bean of type 'com.ust.webmini.SomeService' in your configuration.

UnsatisfiedDependencyException: Error creating bean with name 'helpController' [...]
NoSuchBeanDefinitionException: No qualifying bean of type 'com.ust.webmini.SomeService' available: expected at least 1 bean

Solution

  • @MockMvcTest was built to provide an easy way to unit test a specific controller. It will not scan for any @Service, @Component or @Repository beans, but it will pick up anything annotated with @SpyBean or @MockBean.

    @MockBean will create a mock of the specified type just like Mockito.mock(...) would but it also adds the mocked instance to the spring application context. Spring will then try to inject beans into your controller. So spring essentally does the same thing that you are doing here:

    private SomeService someService = Mockito.mock(SomeService.class);
    private HelpController adviceController = new HelpController(someService);
    

    I'd recomment to just stick with the @MockBean approach. Also if you need to access your HelpController, just autowire it in your test.

    From the docs:

    Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, 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.