使用JUnit5和H2数据库实现SpringBoot应用的功能测试

在实施每日构建的开发团队中,通常都会要求进行自动化的单元测试和功能测试。在数据库类型的应用中,功能测试的难点就是对数据的准备。本文介绍一种通过H2 内存数据库来进行功能测试的方法。

环境说明

在本示例中,分别采用了如下一些软件版本:

Spring Boot: 2.2.5
JUnit: 5.2.0

案例以一个简单的登录服务(Rest Service)为案例。 用户提供用户名和密码,如果验证成功,服务放回登录凭证(token), 如果是非法yoghurt,则返回错误信息。

Spring Boot 服务的基本构建可以参考 Spring Boot 构建Rest服务实验手册(一),本文主要展示项目构建好以后如何借助H2数据库实施自动化的功能测试。

准备依赖库

因为要使用 H2 作为测试库,所以需要在项目中添加 H2 的依赖项, 在 pom.xml 文件中加入:

1
2
3
4
5
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

在 Spring Boot 2.2.5 版本中生成的 pom.xml 文件中,默认是使用 JUnit 4 的版本,要替换为 JUnit 5,需要做如下的改动。

  1. 排除(exclusions) spring-boot-starter-test 中已有的 JUnit4
1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
  1. 添加 JUnit 5的以来项
1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
  1. 为了能在 Maven 命令行中执行(每日构建中需要)测试,需要添加正确的 plugin:
1
2
3
4
5
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version>
</plugin>

准备 H2 测试数据库和测试数据

  1. 在 src/test/resources 中添加名为 application.yml 的文件,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
spring:
datasource:
driverClassName: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password:
jpa:
database: H2
generate-ddl: true
hibernate:
dialect: org.hibernate.dialect.H2Dialect
ddl-auto: create-drop

该配置指定在测试中使用内存数据库,这样可以比较简单的处理测试数据。

  1. 在 data.sql 文件中准备数据。

在 src/test/resources 中添加名为: data.sql 的文件。使用 SQL 语句构建测试数据,比如在本例中就可以简单的加入如下的 SQL 语句:

1
insert into Account_Bean (id, name, pwd) values (1, 'tom', '123');

对应的实体对象为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class AccountBean {

@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Integer id;

private String name;

private String pwd;

private String token;

}

编写测试案例

对于登录功能,我们在Web服务层实现如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
@RequestMapping("/v1/api/sec")
public class SecurityApi {

@Autowired
private SecurityBiz securityBiz;

@PostMapping("/login")
public LoginResult login(@RequestBody LoginForm loginForm) {


LoginResult res = new LoginResult();

String token = securityBiz.loginWithNameAndPassword(loginForm.getName(), loginForm.getPwd());
res.setToken(token);

return res;
}
}

注意: 这里返回的 LoginResult 对象会被拦截器统一转换为 ApiResult 的格式

正确登录的案例

在 src/test/java 相应的包中添加测试类 SecurityApiTest: 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = ElearingApiApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SecurityApiTest {

@LocalServerPort
private int port;

@Autowired
private TestRestTemplate restTemplate;

@Test
public void testLogin() throws Exception {

LoginForm loginForm = new LoginForm();
loginForm.setName("tom");
loginForm.setPwd("123");

ApiResult res = this.restTemplate.postForObject("http://localhost:" + port + "/v1/api/sec/login", loginForm, ApiResult.class);

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

可以看到,我们用 JUnit5 中新的 ExtendWith 代替了 JUnit 4 中的 RunWith, 并使用了 SpringBoot 中的 SpringExtension 类。

在测试案例中,我们没有使用 Mock 对象,而是通过 TestRestTemplate 对象直接访问启动的 Spring Boot 服务, 服务的执行也是直接通过原有的代码访问数据库,最大限度的模拟的用户的操作。

验证是否将 token 记录在数据库中

在真是的系统中,不但要将生成的用户凭证(token)发给用户,也需要将 token 写入到数据库中,对于写入的结果,我们也可以在案例中进行测试。修改测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

@Autowired
private AccountRepos accountRepos;

@Test
public void testLogin() throws Exception {

LoginForm loginForm = new LoginForm();
loginForm.setName("tom");
loginForm.setPwd("123");

ApiResult res = this.restTemplate.postForObject("http://localhost:" + port + "/v1/api/sec/login", loginForm, ApiResult.class);

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

Optional<AccountBean> opAccountBean = accountRepos.findById(1);
assertTrue(opAccountBean.isPresent());
if (opAccountBean.isPresent()) {
AccountBean accountBean = opAccountBean.get();
assertNotNull(accountBean.getToken());
}
}

可以看到,我们增加了 AccountBean 对于的数据访问对象 AccountRepos, 用来直接操作数据库。然后再代码中通过 findById 的方式找到数据库中的记录,并验证 token 是否存在。当然也可以进一步验证 Token 值。

测试登录不成功的情况

用以下代码,可以验证当输入的密码错误时的情况。

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

LoginForm loginForm = new LoginForm();
loginForm.setName("tom");
loginForm.setPwd("xxx");

ApiResult res = this.restTemplate.postForObject("http://localhost:" + port + "/v1/api/sec/login", loginForm, ApiResult.class);

assertNotNull(res);
assertFalse(res.isSucc());
assertEquals("SEC-001", res.getCode());
assertNull(res.getData());

}

与正确的情况相比,主要是验证结果为 False,错误码为特定的,定义好的错误码。

本文标题:使用JUnit5和H2数据库实现SpringBoot应用的功能测试

文章作者:梅老师

发布时间:2020年03月02日 - 20:03

最后更新:2020年05月28日 - 16:05

原始链接:https://www.mls-tech.info/java/springboot-junit5-h2-functiona-test/

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