在 Spring Boot 1.4 中测试 Spring MVC 切片的问题

Posted

技术标签:

【中文标题】在 Spring Boot 1.4 中测试 Spring MVC 切片的问题【英文标题】:Issue with testing Spring MVC slice in SpringBoot 1.4 【发布时间】:2016-10-31 07:00:55 【问题描述】:

我正在尝试新的 Spring Boot 1.4 MVC 测试功能。我有以下控制器。

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

我最小的 ProductService 实现是:

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


ProductRepository的代码是:

public interface ProductRepository extends CrudRepository<Product,    
 Integer>

我正在尝试使用新的@WebMvcTest 来测试控制器。我的观点是 thymeleaf teamplate。我的控制器测试是这样的:

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

但是,在运行测试时出现此错误。

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: 

我需要帮助来解决问题以正确测试 ProductController。非常感谢您提出额外的 andExpect() 建议,以便对控制器进行更彻底的测试。

提前致谢。

【问题讨论】:

【参考方案1】:

以防万一您添加了以下装饰器但仍然无法正常工作:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SomeTest 

    @Autowired
    private MockMvc mockMvc;


    @Test
    public void somePositiveTest() throws Exception 
        mockMvc.perform(MockMvcRequestBuilders.get(url))
                .andExpect(status().is2xxSuccessful());
    

确保您已将以下依赖项添加到您的 pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

【讨论】:

【参考方案2】:

我没有像这样在设置阶段实例化 mockmvc 对象,而不是自动连接 MockMvc。

protected void setUp() 
        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    

【讨论】:

【参考方案3】:

有兴趣加载完整应用程序的人应尝试结合使用@SpringBootTest@AutoConfigureMockMvc 而不是@WebMvcTest

我一直在为这个问题苦苦挣扎,但最终我得到了完整的画面。 互联网上的许多教程,以及我目前找到的官方 Spring 文档,声明您可以使用 @WebMvcTest 测试您的控制器;这是完全正确的,但仍然省略了一半的故事。 正如此类注释的 javadoc 所指出的,@WebMvcTest 仅用于测试您的控制器,根本不会加载您应用的所有 bean,这是设计使然。 它甚至与 @Componentscan 这样的显式 bean 扫描注解不兼容。

我建议任何对此事感兴趣的人阅读注释的完整 javadoc(只有 30 行长,并且塞满了浓缩的有用信息),但我会提取一些与我的情况相关的宝石。

来自Annotation Type WebMvcTest

使用此注解将禁用完全自动配置,而仅应用与 MVC 测试相关的配置(即 @Controller@ControllerAdvice@JsonComponent 过滤器、WebMvcConfigurerHandlerMethodArgumentResolver bean,但不是 @Component@Service@Repository 豆子)。 [...] 如果您希望加载完整的应用程序配置并使用 MockMVC,则应考虑将 @SpringBootTest@AutoConfigureMockMvc 结合使用,而不是此注释

实际上,只有@SpringBootTest + @AutoConfigureMockMvc 解决了我的问题,所有其他使用@WebMvcTest 的方法都无法加载一些所需的bean。

编辑

我收回我对 Spring 文档所做的评论,因为我不知道使用 @WebMvcTest 时暗示了 slice;实际上,MVC 切片文档清楚地表明,并非所有应用程序都已加载,这是切片的本质。

Custom test slice with Spring Boot 1.4

测试切片是对为您的测试创建的 ApplicationContext 进行分段。通常,如果您想使用 MockMvc 测试控制器,您肯定不想打扰数据层。相反,您可能希望模拟控制器使用的服务并验证所有与 Web 相关的交互是否按预期工作。

【讨论】:

恰到好处。 @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc 用于测试类,@Autowired 用于private MockMvc mockMvc; 你是救命的兄弟!【参考方案4】:

您在使用@WebMvcTest 的同时还手动配置了MockMvc 实例。这没有任何意义,因为@WebMvcTest 的主要目的之一是为您自动配置MockMvc 实例。此外,在您的手动配置中,您使用的是standaloneSetup,这意味着您需要完全配置正在测试的控制器,包括将任何依赖项注入其中。您没有这样做会导致NullPointerException

如果您想使用@WebMvcTest,我建议您这样做,您可以完全删除您的setUp 方法,并使用@Autowired 字段注入自动配置的MockMvc 实例。

然后,要控制ProductController 使用的ProductService,您可以使用新的@MockBean 注释创建一个模拟ProductService,然后将其注入ProductController

这些更改使您的测试类看起来像这样:

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

    

【讨论】:

@MockBean 添加到模拟ProductService 会引发NullPointerException,我已经在github 上发布了我的代码here 我错过了您手动配置MockMvc 实例。我已经更新了答案 Autowiring MockMvc 似乎默认启用了安全性,因此报告java.lang.AssertionError: Status Expected :200 Actual :401。通过扩展WebSecurityConfigurerAdapter 和覆盖configure(HttpSecurity httpSecurity) 以允许所有对//products 的GET 请求来配置安全性也没有帮助。最后不得不通过控制器测试类上的@AutoConfigureMockMvc(secure=false) 禁用安全性,现在它就像一个魅力。非常感谢您解决了我测试 MVC 切片的核心问题。 我的设置与您的示例完全一样,但我在Service 上获得了RepositoryNoSuchBeanDefinitionException。对于 @Controller 依赖项使用 @MockBean 是否需要额外的东西? ^ NVM,看起来像一个未解决的问题。 github.com/spring-projects/spring-boot/issues/6663

以上是关于在 Spring Boot 1.4 中测试 Spring MVC 切片的问题的主要内容,如果未能解决你的问题,请参考以下文章

使用 h2database 进行 Spring boot 1.4 测试(在每个测试中回滚)

Spring Boot 1.4测试的简单理解

Spring Boot 1.4 - REST API 测试

Spring boot 1.4 测试:配置错误:发现@BootstrapWith 的多个声明

启用安全性的 Spring Boot 1.4 测试?

spring boot使用profile来区分正式环境配置文件与测试环境配置文件