【问题标题】:Spring Boot WebMvc Tests not including Spring SecuritySpring Boot WebMvc 测试不包括 Spring Security
【发布时间】:2017-07-08 18:00:15
【问题描述】:

我正在开发一个具有一定数量控制器的 Spring Boot MVC 应用程序。 我的根控制器是:

@Controller
@RequestMapping("/")
public class RootController {

    @GetMapping
    public String showStartPage() {
        log.info("GET: Show home page");
        return "index";
    }
}

我已经成功地为控制器实现了 MVC 测试。我的 RootController 的测试是:

@RunWith(SpringRunner.class)
@WebMvcTest(RootController.class)
public class RootControllerMvcTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void testRoot() throws Exception {
        mvc.perform(get("/").accept(MediaType.TEXT_HTML))
            .andExpect(status().isOk())
            .andExpect(view().name("index"));
    }
}

问题:

但是,当我介绍 Spring Security 身份验证和授权时,所有 mvc 控制器测试都失败了。根控制器测试的断言错误是:

java.lang.AssertionError: Status 
Expected :200
Actual   :401

我的安全配置是:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/", "/fonts/*").permitAll()
            .antMatchers("/user/**").hasAuthority("ADMIN")
            .anyRequest().fullyAuthenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .failureUrl("/login?error")
            .usernameParameter("email")
            .permitAll()
            .and()
            .logout()
            .logoutUrl("/logout")
            .deleteCookies("remember-me")
            .logoutSuccessUrl("/")
            .permitAll()
            .and()
            .rememberMe();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(userDetailsService)
            .passwordEncoder(new BCryptPasswordEncoder());
    }
}

解决方案:

然后,我设法解决了这个问题:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class RootControllerMvcTest {
...
}

在这种情况下,我的测试会加载整个应用程序上下文。

我的问题是:

  1. 如何将我的 mvc 测试与身份验证和授权过程分开,只测试控制器的逻辑?
  2. 测试身份验证和授权实现的最佳实践是什么?我是否必须为此使用@SpringBootTest?
  3. 单独测试我的控制器和安全逻辑是否是一个好的决定?

谢谢。

【问题讨论】:

  • 谢谢。这是一个有用的链接。但首先,我想弄清楚是否可以使用 MockMvc 而完全不使用 Spring Security 来测试我的控制器。

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


【解决方案1】:

之前提出的方法在spring的较新版本中似乎是deprecated

另一方面,现在可以将相同的功能用作附加参数。

例如,在我的情况下,WebMvcTest 我是从

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

@RunWith(SpringRunner.class)
@WebMvcTest(value = ImportController.class, secure = false)

一切正常。

【讨论】:

  • secure 也已弃用。改用@AutoConfigureMockMvc(addFilters = false)
【解决方案2】:

很多这可能是意见,但我会给我两分钱。首先,目标应该是覆盖面和上市时间。那就是我需要一个相当快的解决方案来实施和生产可以在生产中工作的软件。所以我不太关心意识形态。

为此,我喜欢混合使用单元测试和集成测试。在单元测试试图实现接近 100% 的代码覆盖率的地方,集成更多地关注与其他系统的交互,例如数据库、客户端、其他服务等。顺便说一句,单元测试和集成测试都必须自动化,因此集成应该在没有人在场的情况下完成。

我过去曾使用两种方法对我的控制器进行单元测试。一种是在 Spring 上下文之外直接调用控制器方法。我们能够达到我们的代码覆盖率目标。这工作了一段时间,但当我们开始使用 Spring 的 HATEOS 功能时失败了,该功能依赖于使用 Spring MVC 上下文来创建 HATEOS 所基于的链接。所以我们切换到 MockMvc 进行单元测试。这奏效了,我们达到了覆盖目标。现在,在您的情况下,问题是安全性妨碍了您的单元测试。我会考虑绕过安全配置,以便我可以达到我的覆盖目标,可以在集成测试中测试安全性。这可以通过在 SecurityConfig 类上使用 @Profile 注释来完成。在我们的微服务网络中,我们使用@Profile 来启用和禁用配置类。例如,我们仅在使用某个配置文件时才注册到我们的 Eureka 服务器。有不同的方法可以做到这一点。一种方法是遵循我们为 Eureka 配置所做的操作,仅当我们称为 active-eureka 的配置文件存在时才启用,我们创建了这个名称。这可以防止在我们从 IDE 运行微服务时联系 Eureka 服务器。在您的情况下,您可以拥有一个名为 activate-security 的配置文件。这将允许您在安全性破坏乐趣的情况下进行基于 MockMvc 的单元测试。

现在单元测试已经结束,您仍然必须在启用安全性的情况下对您的服务进行集成测试。对此有不同的方法。我们使用的一种方法是将@SpringBootTest 注释与TestRestTemplate 类一起使用。这将启动我们的微服务,然后我们使用TestRestTemplate 对其进行真正的服务调用。 TestRestTemplate 是一个真正的 http RestTemplate 客户端,除了它知道您的 Web 服务在哪个端口上运行,因此您可以在尝试找出端口时删除一些样板代码。使用TestRestTamplte,您可以省略URL 的http://host:port 部分。它被描述为here。请注意,如果我们像您一样启用了安全性,那么我们将需要通过该安全性,就好像我们是生产中的真正客户一样,这意味着我们不会避开安全性而是积极参与,这与我们在单元测试期间做了什么。

我们对单元测试和集成测试有不同的目标。使用上述方法,我们可以适当地独立专注于这些目标。

【讨论】:

  • 感谢@Jose 的详细回答。我已经成功地将我的控制器测试逻辑与@Profile 分离。不过,我的实现方式略有不同。您的最佳实践和集成测试建议真的很有帮助!他们将帮助我实施集成测试。
【解决方案3】:

答案1:


我从 MVC 测试中排除了 Spring Security 功能,如下所示:
  1. 添加src/test/java/resources/application-disabled-security.yml:

    安全性: 基本的: 启用:假

  2. 在 mvc 测试中添加了 @ActiveProfiles("disabled-security") 注释。

【讨论】:

    猜你喜欢
    • 2018-01-02
    • 2021-06-01
    • 2021-02-08
    • 2017-10-21
    • 2018-12-13
    • 1970-01-01
    • 1970-01-01
    • 2011-03-27
    • 2019-04-09
    相关资源
    最近更新 更多