18

I have a controller and a test using @WebMvcTest and its running fine. Now i needed to add a little validation logic and for this i @Autowired an additional bean (a @Component, a MapstructMapper).

As expected now the test is failing due to @WebMvcTest. (No components are discovered)

Is there a way to add one bean to the context created?

Since i am using @MockBeans to mock service layer: is there a way to delegate all mock calls to a real object? With this i could mock the mapper and delegate to real mapper?!

officer
  • 2,080
  • 1
  • 20
  • 29
dermoritz
  • 12,519
  • 25
  • 97
  • 185

2 Answers2

24

A very simple solution is to annotate your test class with @Import specifying the class(es) of the additional bean(s) you want to use in your test, as stated in documentation:

Typically @WebMvcTest is used in combination with @MockBean or @Import to create any collaborators required by your @Controller beans.

e.g.

@WebMvcTest(MyController.class)
@Import(SomeOtherBean.class)
public class SourcingOrganisationControllerTests {

    // The controller bean is made available via @WebMvcTest  
    @Autowired
    private MyController myController;

    // Additional beans (only one in this case) are made available via @Import
    @Autowired
    private SomeOtherBean someOtherBean;
}
user11153
  • 8,536
  • 5
  • 47
  • 50
Dónal
  • 185,044
  • 174
  • 569
  • 824
  • 4
    Omg you saved me. In my case I added a custom filter extending `OncePerRequestFilter` and all of a sudden my `@WebMvcTest` related tests started failing due to not being able to find one of its constructor beans in context. I was going mad. – Naruto Sempai Nov 16 '20 at 00:00
  • 4
    I don't think there's any reason to have an `@Autowired` field for the `@Import`ed bean in the test; it gets injected into the controller, so unless the test code needs to do something with it, you can omit the field declaration in the test. – E-Riz Mar 25 '22 at 14:54
21

A simple way of getting additional beans in the context is via using nested configuration classes within test classes

@TestConfiguration
static class AdditionalConfig {
    @Bean
    public SomeBean getSomeBean() {
        return new SomeBean());
    }
}

Example:

Scenario - If you have some Controller say ProductController and you have the corresponding slice-test for the class say ProductionControllerTest

@RestController
public class ProductController {

    @Autowired
    private IProductService productService;

    @Autowired
    private IProductValidator productValidator;


    @GetMapping("product")
    public Product getProduct(@RequestParam Long id) {

        Product product = productService.getProduct(id); // you are using mockBean of productService

        productValidator.validateProduct(product); // you need real bean of productValidator
        return product;
    }
}

Corresponding slide test class with an additional bean configuration

@RunWith(SpringRunner.class)
@WebMvcTest
public class ProductControllerSliceTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private IProductService productService;

    @Autowired
    private ApplicationContext applicationContext;

    @TestConfiguration
    static class AdditionalConfig {
        @Bean
        public IProductValidator productValidator() {
            return new ProductValidator();
        }
    }


    @Test
    public void testProductGetById() throws Exception {
        Product testProductWithID1L = new Product(1L, "testProduct");
        when(productService.getProduct(anyLong())).thenReturn(testProductWithID1L);

        mockMvc.perform(get("/product")
                .param("id", "1")).andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("name")
                        .value("testProduct"))
                .andExpect(MockMvcResultMatchers.jsonPath("id")
                        .value("1"));
    }
}

Additional thoughts on your scenario: If you really intend to do the unit testing of the controller class then ideally you should mock all the additional dependencies of class that you are testing. Ideally, the intention of the unit test is to only test the behavior of the object/class under test. All the dependent classes behavior or external calls should be mocked.
When you start testing several classes together under one test, you are moving more towards a component test or integration test

Dhruv
  • 10,291
  • 18
  • 77
  • 126
  • Thanks, and you are absolutely right, in my case i injecting a bean of generated code - that maps 1:1 entity to dto. This i do to be able to reuse some part of validation. (I want to prevent an exception if invalid dto hits the api). – dermoritz May 09 '19 at 06:05
  • @Dhruv - you are very well aware that such a MVC controller test never calls the method unter test directly but has to dig through a couple of non-mocked servlet and spring methods? IMHO it is absolutely ok to use "domestic" services, especially if it requires much less service-internal knowledge. – wh81752 Jan 13 '23 at 14:27