web-dev-qa-db-fra.com

Problème avec le test de la tranche Spring MVC dans Spring Boot 1.4

J'essaie les nouvelles fonctionnalités de test Spring Boot 1.4 MVC. J'ai le contrôleur suivant.

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

Mon implémentation minimale de ProductService est:

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

}

Le code de ProductRepository est:

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

J'essaie d'utiliser le nouveau @WebMvcTest pour tester le contrôleur. Mon point de vue est une plaque d'équipe de thymeleaf. Et mon test de contrôleur est le suivant:

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

Mais, en exécutant le test, j'obtiens cette erreur.

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: {}

J'ai besoin d'aide pour résoudre le problème afin de tester correctement ProductController. Des suggestions supplémentaires etExpect () pour des tests plus approfondis du contrôleur seront très appréciées.

Merci d'avance.

14
user2693135

Vous utilisez @WebMvcTest tout en configurant manuellement une instance MockMvc. Cela n'a pas de sens comme l'un des principaux objectifs de @WebMvcTest consiste à configurer automatiquement une instance MockMvc pour vous. De plus, dans votre configuration manuelle, vous utilisez standaloneSetup, ce qui signifie que vous devez configurer entièrement le contrôleur en cours de test, y compris y injecter toutes les dépendances. Vous ne faites pas ce qui provoque le NullPointerException.

Si vous souhaitez utiliser @WebMvcTest, et je vous recommande de le faire, vous pouvez supprimer entièrement votre méthode setUp et avoir une instance MockMvc auto-configurée injectée à la place à l'aide d'un @Autowired champ.

Ensuite, pour contrôler le ProductService utilisé par ProductController, vous pouvez utiliser le nouveau @MockBean annotation pour créer une maquette ProductService qui sera ensuite injectée dans ProductController.

Ces modifications laissent votre classe de test ressembler à ceci:

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

    }
}
13
Andy Wilkinson

Les personnes intéressées par le chargement de l'application complète devraient essayer d'utiliser @SpringBootTest combiné avec @AutoConfigureMockMvc plûtot que le @WebMvcTest.

Je me bats avec le problème depuis un bon moment, mais j'ai finalement obtenu l'image complète.
Les nombreux tutoriels sur Internet, ainsi que la documentation officielle du printemps que j'ai trouvée jusqu'à présent , indiquez que vous pouvez tester vos contrôleurs à l'aide de @WebMvcTest; c'est tout à fait correct, en omettant toujours la moitié de l'histoire.
Comme le souligne le javadoc d'une telle annotation, @WebMvcTest est uniquement destiné à tester vos contrôleurs et ne chargera pas du tout tous les beans de votre application , et cela est voulu par la conception même du produit.
Il est même incompatible avec les annotations d'analyse de bean explicites comme @Componentscan.

Je suggère à toute personne intéressée par la question de lire l'intégralité du javadoc de l'annotation (qui ne fait que 30 lignes et regorge d'informations utiles condensées) mais je vais extraire quelques gemmes pertinentes à ma situation.

de Type d'annotation WebMvcTest

L'utilisation de cette annotation désactivera la configuration automatique complète et n'appliquera à la place que la configuration pertinente aux tests MVC (c'est-à-dire @Controller, @ControllerAdvice, @JsonComponent Filtre, WebMvcConfigurer et HandlerMethodArgumentResolver beans mais pas @Component, @Service ou @Repository des haricots). [...] Si vous cherchez à charger la configuration complète de votre application et à utiliser MockMVC, vous devriez envisager @SpringBootTest combiné avec @AutoConfigureMockMvc plutôt que cette annotation .

Et en fait, seulement @SpringBootTest + @AutoConfigureMockMvc a résolu mon problème, toutes les autres approches utilisant @WebMvcTest n'a pas pu charger certains des beans requis.

ÉDITER

Je reprends mon commentaire que j'ai fait sur la documentation de Spring, car je ne savais pas qu'une tranche était implicite quand on utilise un @WebMvcTest; en fait, la documentation des tranches MVC indique clairement que toutes les applications ne sont pas chargées, ce qui est par la nature même d'une tranche.

tranche de test personnalisée avec Spring Boot 1.4

Le découpage de test consiste à segmenter le ApplicationContext créé pour votre test. En règle générale, si vous souhaitez tester un contrôleur à l'aide de MockMvc, vous ne voulez certainement pas vous soucier de la couche de données . Au lieu de cela, vous voudrez probablement vous moquer du service utilisé par votre contrôleur et valider que toutes les interactions liées au Web fonctionnent comme prévu.

21
Antonio

Au lieu de câbler automatiquement MockMvc, j'ai instancié un objet mockmvc dans une phase de configuration comme celle-ci.

protected void setUp() {
        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
0
cammando