spring-mvcspring-bootspring-test-mvc

Issue with testing Spring MVC slice in SpringBoot 1.4


I'm trying out the new Spring Boot 1.4 MVC testing features. I have the following controller.

@Controller
public class ProductController {

  private ProductService productService;

  @Autowired
  public void setProductService(ProductService productService) {
    this.productService = productService;
  }

  @RequestMapping(value = "/products", method = RequestMethod.GET)
  public String list(Model model){
    model.addAttribute("products", productService.listAllProducts());
     return "products";
  }
}

My minimal ProductService implementation is:

@Service
public class ProductServiceImpl implements ProductService {
  private ProductRepository productRepository;

  @Autowired
  public void setProductRepository(ProductRepository productRepository) {
    this.productRepository = productRepository;
  }

  @Override
  public Iterable<Product> listAllProducts() {
    return productRepository.findAll();
  }

}

The code of ProductRepository is:

public interface ProductRepository extends CrudRepository<Product,    
 Integer>{
}

I'm trying to use the new @WebMvcTest to test the conroller. My view is a thymeleaf teamplate. And my controller test is this:

@RunWith(SpringRunner.class)
@WebMvcTest(ProductController.class)

public class ProductControllerTest {
private MockMvc mockMvc;

@Before
public void setUp() {
    ProductController productController= new ProductController();       
    mockMvc = MockMvcBuilders.standaloneSetup(productController).build();
}

@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"));               
 }
}

But, on running the test I get this error.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'productController': Unsatisfied dependency expressed through method 'setProductService' parameter 0: No qualifying bean of type [guru.springframework.services.ProductService] found for dependency [guru.springframework.services.ProductService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [guru.springframework.services.ProductService] found for dependency [guru.springframework.services.ProductService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

I need help to resolve the issue to properly test ProductController. Suggestions for additional andExpect() for more thorough testing of the controller will be highly appreciated.

Thanks in advance.


Solution

  • 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())));
    
        }
    }