转载

Spring Security 测试实战

引言

试题管理系统的安全模块使用 Spring Security ,代码从原华软仓库移植,在移植的过程中,发现原测试编写的不好,遂在新系统中对安全模块测试进行了重构。

Spring 测试

添加 @SpringBootTest 注解,意为这是一个基于 SpringBoot 的单元测试。

SpringBoot 在官方的 Guide 中提供了多种测试方式。

@SpringBootTest 注解中的 webEnvironment 属性可以配置测试环境,默认为 MOCK 环境。

/**
 * The type of web environment to create when applicable. Defaults to
 * {@link WebEnvironment#MOCK}.
 * @return the type of web environment
 */
WebEnvironment webEnvironment() default WebEnvironment.MOCK;

模拟环境测试

启用 Spring Security 后,单元测试中对 api 的测试会被 Spring SecurityFilter 进行拦截,所以测试之前需要进行用户登录操作。

之前都是使用比较笨重的方法,写一个 @Before@Before 里进行登录,之后再执行测试方法。

最近在阅读 Spring Security Test 文档之后,终于找到一种模拟登录十分简便的方法, @WithMockUser

test method with mock user - spring security test

引入 Spring Security Test 依赖:

<!-- Spring Security Test -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

示例代码如下:

@SpringBootTest
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@WithMockUser(username = "admin", password = "admin")
public class ControllerTest {

    @Autowired
    protected MockMvc mockMvc;

    @Test
    void contextLoads() {
    }
}

注: @RunWith(SpringRunner.class) 表示当前测试使用 org.springframework.test.context.junit4.SpringRunner 类来执行,最新的 SpringBoot 版本中已经全面启用 junit5 ,不推荐使用 junit4.SpringRunner ,因为未经过内部学习与测试,未在生产项目中使用。

真实环境测试

为了减少学习与沟通的成本,之前,所有的测试规定都在 MOCK 环境下,使用 MockMVC 进行 api 测试。

虽然 MOCK 环境能解决大部分的问题,并且可以在不启动 Server 的情况下直接进行测试,但在某些场景下,仍需要真实环境下的 HTTP 服务与请求测试。

启用 Spring Security 后, MockMVC 是直接测试控制器,并非在真实的 HTTP 服务器下进行测试, MOCK 环境中使用的是 MockHttpSession ,这不是标准的 Session 实现,没有加入对 COOKIE 的支持,所以在测试安全模块时,无法像浏览器一样测试 COOKIE 等认证信息。

spring mockmvc doesn't contain cookies - stackoverflow

StackOverflow 上也没有解决方案,答案推荐使用 TestRestTemplate +真实的服务器环境进行单元测试。

webEnvironment 配置为 SpringBootTest.WebEnvironment.RANDOM_PORT ,即表示当前测试在一个随机端口的真实 Web 环境下运行。

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class AuthControllerTest {
    @Autowired
    private TestRestTemplate restTemplate;
}

测试时使用 TestRestTemplate 进行网络请求的发送,真实模拟 Web 服务器环境。

示例代码如下:

logger.debug("3: 测试用户名密码正确");
username = RIGHT_USERNAME;
password = RIGHT_PASSWORD;
response = this.restTemplate
        .withBasicAuth(username, password)
        .getForEntity(CONFIG_LOGIN, Void.class);

logger.debug("断言: 状态码为200");
assertThat(response.getStatusCode().value()).isEqualTo(HttpStatus.OK.value());

logger.debug("获取 response 的 Set-Cookie 信息,并断言");
String setCookie = response.getHeaders().getFirst(HttpHeaders.SET_COOKIE);
assertThat(setCookie).isNotNull();

总结

两个各有优点,之前我们一直使用简单方便的 Mock 环境进行测试,但当我们有一天,发现这个 Mock 环境测试下的 MockHttpSession 无法满足需求的时候,我们开始探索其他的测试方案。

真正的掌握,不是知道什么时候用,而是知道什么时候不用。

原文  https://segmentfault.com/a/1190000021122222
正文到此结束
Loading...