不用Mock测试SpringBoot应用中的Filter

在 Spring Boot 应用的开发中,为了测试 Filter, 通常要使用 Mock 的方法。 但使用 Mock 作为一个模拟对象机制,割裂了系统组件之间应有的联系,容易造成 Mock 测试通过了,但实际上线确出错的情况,特别是在维护和重构时。 本文介绍一种不使用 Mock 对象测试 Filter 的方法。

本文的环境和案例基于 使用JUnit5和H2数据库实现SpringBoot应用的功能测试,如果不熟悉的读者,可以先参考该文。

构建应用中的 Filter

在很多提供 Restful 服务的系统中,通常通过用户凭证(token)来确认,跟踪用户的身份、合法性及用户权限。对于受保护的URL, 通常需要验证客户端发送的 token, 通常客户端在发起请求时,在 HTTP Request 的 Body 或是 Http Request 的 Header 中携带 token。每个受保护的URL需要首先验证该 token, 然后再确定用户的合法性和权限。因为有很多URL需要加入这个逻辑,为了避免重复代码,所以我们自然想到用 Filter 来完成。 示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Slf4j
@Component
public class SecurityFilter implements Filter {

@Autowired
private AccountRepos accountRepos;

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) servletRequest;

String token = req.getHeader(HEADER_AUTH_TOKEN_KEY);

if (token == null) {
throw new BizException(BIZ_LOGIN_FIRSTLY);
}

// TODO: Get account from Cache
Optional<AccountBean> optionalAccountBean = accountRepos.findByToken(token);
if (optionalAccountBean.isPresent()) {
AccountBean accountBean = optionalAccountBean.get();
req.setAttribute(ATTR_ACCOUNT_BEAN_KEY, accountBean);
filterChain.doFilter(servletRequest, servletResponse);
} else {
throw new BizException(BIZ_LOGIN_FIRSTLY);
}
}
}

在通常的 Web 应用中,当验证不通过时,通常强制跳转到登录页面,但 Restful 服务应用中,我们直接返回错误信息。

构建测试服务和测试数据

因为不使用 Mock 对象,所以我们选哟构造一个专门用于测试的服务,为了不让构造的测试服务发布到生产环境中,我们将测试服务构造到 src/test/java 目录下。 在 src/test/java 中新建包: filter, 然后在包中新建一个 RestController。 代码如下:

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/v1/api/auth")
public class FliterTestController {

@PostMapping("/test")
public String testFilter() {
return "{\"res\":\"ok\"}";
}
}

在测试方法中,我们简单的返回要给 json 串。 当然,也可以返回定义好的实体对象。

在 data.sql 文件中加入测试数据,如下:

1
insert into Account_Bean (id, name, pwd, token) values (2, 'jack', '123', 'token-001');

编写测试方法

测试方法与测试 RestController 的一致。比如要测试 token 错误的情况,可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testAuthFilterWithNotExistToken() {

HttpHeaders headers = new HttpHeaders();
headers.set(Constants.HEADER_AUTH_TOKEN_KEY, "token-xxx");

HttpEntity request = new HttpEntity(headers);

ApiResult res = this.restTemplate.postForObject("http://localhost:" + port + "/v1/api/auth/test", request, ApiResult.class);
assertNotNull(res);
assertFalse(res.isSucc());

}

测试没有 Header 中没有带 token 情况可以写成:

1
2
3
4
5
6
7
8
@Test
public void testAuthFilterWithNoToken() {

ApiResult res = this.restTemplate.postForObject("http://localhost:" + port + "/v1/api/auth/test", null, ApiResult.class);
assertNotNull(res);
assertFalse(res.isSucc());

}

测试正常 token 的情况可以这样测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testAuthFilter() {

HttpHeaders headers = new HttpHeaders();
headers.set(Constants.HEADER_AUTH_TOKEN_KEY, "token-001");

HttpEntity request = new HttpEntity(headers);

ApiResult res = this.restTemplate.postForObject("http://localhost:" + port + "/v1/api/auth/test", request, ApiResult.class);

assertNotNull(res);
assertTrue(res.isSucc());

}

需要注意的是,因为测试服务是写在 src/test/java 中的,所以需要在 src/test/java 中增加一个使用了@SpringBootApplication注解的类来加载 RestController

该类很简单,如下:

1
2
3
@SpringBootApplication
public class ElearningApiApplicationTestRun {
}

在测试类中,需要在@SpringBootTest 注解中增加对 ElearningApiApplicationTestRun 的引用。如下:

1
2
3
4
5
6
7
8
9
10
11
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {ElearingApiApplication.class, ElearningApiApplicationTestRun.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SecurityFilterTest {

@LocalServerPort
private int port;

@Autowired
private TestRestTemplate restTemplate;

....

本文标题:不用Mock测试SpringBoot应用中的Filter

文章作者:Morning Star

发布时间:2020年03月03日 - 18:03

最后更新:2021年04月16日 - 15:04

原始链接:https://www.mls-tech.info/java/springboot-test-filter-non-mock/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。