在 Spring Boot 1.4 MVC 测试中使用 @WebMvcTest 设置 MockMvc

Posted

技术标签:

【中文标题】在 Spring Boot 1.4 MVC 测试中使用 @WebMvcTest 设置 MockMvc【英文标题】:Setting up MockMvc with @WebMvcTest in Spring Boot 1.4 MVC Testing 【发布时间】:2016-11-05 05:00:39 【问题描述】:

我几乎没有工作代码可以使用新的 Spring Boot 1.4 @WebMvcTest 以不同的方式设置 MockMVc。我了解standaloneSetup 方法。我想知道的是设置MockMvcWebApplicationContext 和自动装配MockMvc 之间的区别。

代码片段 1:通过 WebApplicationContext 设置的 MockMvc

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = ProductController.class)
public class ProductControllerTest 

private MockMvc mockMvc;

@Autowired
private WebApplicationContext webApplicationContext;

@MockBean
private ProductService productServiceMock;

@Before
public void setUp() 
     mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();


@Test
public void testShowProduct() throws Exception       

    Product product1 = new Product();
    /*Code to initialize product1*/

    when(productServiceMock.getProductById(1)).thenReturn(product1);

    MvcResult result = mockMvc.perform(get("/product/id/", 1))
            .andExpect(status().isOk())
            /*Other expectations*/
            .andReturn();
  

根据WebMvcTest API 文档,默认情况下,使用 @WebMvcTest 注释的测试也会自动配置 Spring Security 和 MockMvc。所以,我预计这里会出现 401 Unauthorized 状态码,但测试通过并显示 200 状态码。

接下来,我尝试自动连接MockMvc,但测试失败并显示 401 Unauthorized 状态码,除非我添加 @AutoConfigureMockMvc(secure=false) 或更新 @WebMvcTest 注释以禁用安全性:

@WebMvcTest(controllers = IndexController.class, secure = false)

以下是仅在明确禁用安全性后通过的代码。代码片段 2:通过自动装配的 MockMvc

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = ProductController.class)
@AutoConfigureMockMvc(secure=false)
public class ProductControllerTest 
@Autowired
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@MockBean
private ProductService productServiceMock;

@Test
public void testShowProduct() throws Exception       

    Product product1 = new Product();
    /*Code to initialize product1*/

    when(productServiceMock.getProductById(1)).thenReturn(product1);

    MvcResult result = mockMvc.perform(get("/product/id/", 1))
            .andExpect(status().isOk())
            /*Other expectations*/
            .andReturn();
  

所以我的问题是:

    为什么 Code sn-p 1 没有报告 401 Unauthorized 状态代码错误,而自动接线 MockMvc 报告了。还要重申官方文档所说的 默认情况下,使用 @WebMvcTest 注释的测试也将自动配置 Spring Security 和 MockMvc。 但是,在这种情况下,@WebMvcTest 似乎与自动配置无关Spring Security(因为代码片段 1 通过没有任何 401 错误)。最后归结为我如何设置MockMvc。我说的对吗?

    这两种方法的区别/目标是什么?

    通过@AutoConfigureMockMvc(secure=false) 禁用安全性与通过@WebMvcTest(controllers = IndexController.class, secure = false) 禁用安全性有何不同。哪个是首选方法或何时(或在何处)使用它们?

【问题讨论】:

【参考方案1】:

我不知道这是否正确,但我可以使用下面的方法禁用配置类

@WebMvcTest(ProductController.class)
@ContextConfiguration(classes = ProductController.class)
public class ProductControllerTest 
  @Autowired
  private MockMvc mockMvc;

  @MockBean
  private ProductService productServiceMock;

【讨论】:

【参考方案2】:

我也遇到过类似的问题。 @WebMvcTest 自动使用基本身份验证配置 Spring Security,但我有一个扩展 WebSecurityConfigurerAdapter.WebSecurityConfig 类在这个类中,我禁用了基本身份验证并配置了令牌基础安全性。这意味着WebSecurityConfig 类不用于配置 Spring Security。

为了解决这个问题,我将@ContextConfiguration 添加到我的单元测试类中,并添加了WebSecurityConfig 类的依赖项的模拟。

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = CategoryRestService.class)
@ContextConfiguration(classes = MjApplication.class, WebSecurityConfig.class)
public class CategoryRestServiceTest 
    
    @MockBean
    private CategoryRepository repository;
    
    @MockBean
    CurrentUserDetailsService currentUserDetailsService;
    
    @MockBean
    TokenAuthProvider tokenAuthProvider;
    
    @Autowired
    MockMvc mockMvc;
    
    private MediaType contentType = new    MediaType(MediaType.APPLICATION_JSON.getType(),
            MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
    
    
    @Test
    public void getCategories() throws Exception 
        Category category1 = new Category();
        category1.setName("Test Category 1");
        category1.setId(1L);
        Category category2 = new Category();
        category2.setName("Test Category 2");
        category2.setId(2L);
        List<Category> categoryList = new ArrayList<Category>();
        categoryList.add(category1);
        categoryList.add(category2);
        given(this.repository.findAll())
        .willReturn(categoryList);
        mockMvc.perform(get("/public/rest/category"))
        .andExpect(status().isOk())
        .andExpect(content().contentType(contentType))
        .andExpect(jsonPath("$[0].id", is(1)))
        .andExpect(jsonPath("$[0].name", is("Test Category 1")))
        .andExpect(jsonPath("$[1].id", is(2)))
        .andExpect(jsonPath("$[1].name", is("Test Category 2")));
    


【讨论】:

【参考方案3】:

我不确定这是否直接相关,但是有一个 outstanding bug,如果使用 spring boot 和 @WebMvcTest,您的自定义 @EnableWebSecurity 配置类将被忽略。错误报告中提到了几个解决方法。我正在使用:

@WebMvcTest(includeFilters = @Filter(classes = EnableWebSecurity.class))

【讨论】:

【参考方案4】:

根据github中的这个问题

https://github.com/spring-projects/spring-boot/issues/5476

@WebMvcTest 默认自动配置,当 spring-security-test 在类路径中时进行基本身份验证

回答您的问题:

    在代码 sn-p 1 中,您没有在测试类中注入 MockMvc,您应该在 setup 方法的 builder 中添加 .apply(springSecurity()),因此 spring 将使用基本配置(不是您的自定义安全配置(如果有) 这两种方法的作用基本相同,不同之处在于第二种方法已经在 MockMvc 中提供了基本身份验证,这就是您必须使用 secure=false 的原因 来自文档:

默认情况下,使用 @WebMvcTest 注释的测试也会自动配置 Spring Security 和 MockMvc(包括对 htmlUnit WebClient 的支持 和 Selenium WebDriver)。为了更细粒度地控制 MockMVC 可以使用@AutoConfigureMockMvc 注解。

【讨论】:

以上是关于在 Spring Boot 1.4 MVC 测试中使用 @WebMvcTest 设置 MockMvc的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring Boot MVC 测试中自定义 bean

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

Spring Boot 1.4测试的简单理解

Spring Boot 1.4 - REST API 测试

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

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