【问题标题】:Integration Testing Spring Boot With MockMVC使用 MockMVC 集成测试 Spring Boot
【发布时间】:2015-01-01 22:34:34
【问题描述】:

我在使用 MockMvc 测试 Spring Boot 应用程序时遇到了一些问题。

我有以下测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {SpringConfiguration.class, SecurityConfiguration.class})
@IntegrationTest({"server.port=8080"})
@WebAppConfiguration
public class DemoTest {

@Autowired
private EmbeddedWebApplicationContext webApplicationContext;

private MockMvc mockMvc;

@Before
public void setUp() throws Exception {
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}

@Test
public void testGetAccountUnauthenticated() throws Exception {
    mockMvc.perform(get("/accounts/1").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isUnauthorized());
}
}

这会导致 HTTP 200 而不是 401。我启用了组件扫描和自动配置,并且在我的 SecuityConfiguration 类中配置了 spring 安全性,如下所示:

@Configuration
@EnableWebSecurity
@EnableWebMvcSecurity // required for use of @AuthenticationPrincipal in MVC controllers.
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Override
public void configure(WebSecurity web) {
    web.debug(true);
}

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    //set up authentication.
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated();
   // set up form login
}
}

如果我使用 RestTemplate 访问 http://localhost:8080/accounts/1,那么我会得到预期的行为 (HTTP 401)。

我看到其他示例(例如Spring Boot setup security for testing)建议我自动连接FilterChainProxy 并使用WebApplicationContext.addFilters(filterChainProxy) 方法手动添加过滤器。但是,这对我来说实际上是失败的 (org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.security.web.FilterChainProxy] found)。

我有两个问题:

  1. 为什么注入的 WebApplicationContext 不会自动使用 SpringSecurity 过滤器?即使我可以获取 FilterChainProxy 并手动添加,JavaDoc for EmbeddedWebApplicationContext 状态

上下文中定义的任何 {@link Servlet} 或 {@link Filter} bean 都将自动注册到嵌入式 Servlet 容器中

因此,我不希望手动添加安全过滤器链,因为我(错误地?)由于 Spring Boot 中的自动配置魔法,我希望它“正常工作”?

  1. 为什么应用上下文中没有FilterChainProxy?同样,也许我对 AutoConfiguration 的期望不正确 - 但我认为这将被配置为上下文配置的一部分。

提前感谢您的任何建议。


编辑

  • FilterChainProxy 没有被注入的原因是我的配置设置为

    公共无效配置(WebSecurity web){ web.debug(true); }

这实际上配置了org.springframework.security.web.debug.DebugFilter。无论此调试设置如何,我现在设法获取过滤器的方式如下:

@Resource(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
private Filter securityFilter;

如果我将其添加到 MockMvcBuilder 中,如下所示:

MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilters(securityFilter)

然后它会按预期工作。

但是,我不明白为什么 MockMVC 会忽略过滤器,因为这对于测试请求似乎很重要,因为过滤器中可能发生任何可能影响测试结果的事情。此外,这意味着要正确测试,我需要在 servlet 上下文中查找所有过滤器并建立它们的优先级/url 映射并适当地添加它们。这似乎容易出错且没有必要。

【问题讨论】:

  • 好的,所以我还没有解决问题。但是,我相信 MockMVC 只是忽略了 EmbeddedWebApplicationContext 上的任何配置过滤器。在我的调试器中,如果我评估表达式webApplicationContext.getServletContext().getFilterRegistrations(),我可以看到一堆过滤器,包括“springSecurityFilterChain”。虽然这并没有向我解释为什么不能注入 FilterChainProxy,但它也提出了一个问题,即为什么 MockMvc 会简单地忽略在 Servlet 上下文中配置的过滤器。
  • 正确。这就是它被称为 MockMVC 的原因(它主要用于测试“DispatcherServlet”和 Spring MVC)。但是您的测试中有一个完整的应用程序在端口 8080 上运行,那么如果您想要端到端测试,为什么不直接使用 RestTemplate 来实现呢?
  • 我选择使用 MockMVC 而不是 RestTemplate 的主要原因是我觉得 API 更易于使用,从而产生更简洁的测试代码并符合预期 - 例如“.andExpect(status().isUnauthorized())”。其次,我觉得测试以不同用户的身份发出请求会稍微容易一些,而不必担心传递特殊的 cookie 值或请求标头以确保我作为给定用户进行了身份验证。无论哪种方式,我都决定完全按照您的建议使用 RestTemplate 而不必担心 MockMVC。

标签: spring-security spring-boot spring-test spring-test-mvc mockmvc


【解决方案1】:

我同意 MockMVC 可能更多地用于测试 SpringMVC 和控制器中的自定义代码,正如 @dave-syer 所评论的那样。因此,如果想同时使用您的自定义控制器代码(映射到 URL 的控制器的正确性;输入和输出对象的映射和验证;标准控制器;您的控制器)来测试 Spring MVC 基础架构,而不利用 Servlet 容器部分堆栈,MockMVC 为您服务。

但 MockMVC 也确实有添加过滤器的方法,因此它被设计为可以在所描述的测试类型中使用过滤器。有时过滤器可能会在控制器内部的代码中发挥功能性作用,否则无法使用 MockMVC 进行测试。

考虑到所有这些理论,我试图为我的测试模拟引导行为,其中过滤器将以 Spring Boot 方式设置并由我的测试拾取以与 MockVMC 一起使用。这是我最终使用的一个 sn-p。它肯定可以被增强以更精确地模仿引导行为并提取到一些自定义的 MockMVCBuilder 中。

@Autowired
private WebApplicationContext wac;

private MockMvc mockMvc;

@Before
public void setUp() {
    Collection<Filter> filterCollection = wac.getBeansOfType(Filter.class).values();
    Filter[] filters = filterCollection.toArray(new Filter[filterCollection.size()]);
    mockMvc = MockMvcBuilders.webAppContextSetup(wac).addFilters(filters).build();
}

【讨论】:

    【解决方案2】:

    你试过了吗?

    import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
    
    ...
    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class AuthorizeTest {
    
        @Autowired
        private WebApplicationContext wac;
    
        @Before
        public void setup() {
            this.mockMvc = MockMvcBuilders
                    .webAppContextSetup(wac)
                    .apply(springSecurity())
                    .build();
        }
        ...
    
    }
    

    在我的例子中,它是 403,而不是 401,但你明白了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-25
      • 1970-01-01
      • 1970-01-01
      • 2018-05-25
      • 1970-01-01
      • 2019-01-20
      • 2018-02-01
      • 2020-02-10
      相关资源
      最近更新 更多