引言
相比于单元测试,集成测试能够跨模块执行完整功能,从接口层开始调用到底层对数据库的访问,用更少的 mock 测试功能,在依赖并不复杂的系统中更容易使用。搭建 springboot + junit4 集成测试框架,使用 mockmvc 构造 REST 请求,并使用 h2 拉起一个轻量级的 java 内存数据库。
依赖引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>1.5.16.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
WebAppContextSetup 和 StandaloneSetup
mockmvc 的引入有两种方式:
1.通过 web 环境应用配置引入 - WebAppContextSetup
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class MockMvcTest {
@Autowired
private MockMvc mockMvc;
}
其中,@AutoConfigureMockMvc 注解用于引入 web 环境应用配置。还有一种引入方式是用 @WebAppConfiguration 注解 + MockMvcBuilders.webAppContextSetup:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:config/IncotermsRestServiceTest-context.xml")
@WebAppConfiguration
public class MockMvcTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void before() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
}
2.单独引入用于单元测试 - StandaloneSetup
public class MockMvcTest {
@InjectMocks
private Service service;
private MockMvc mockMvc;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(service).build();
}
}
通过 mockMvc.perform 方法调用 REST 接口
测试 get 接口示例:
mockMvc.perform(get("/user/{id}", 1)) //执行请求
.andExpect(model().attributeExists("user")) //验证存储模型数据
.andExpect(view().name("user/view")) //验证viewName
.andExpect(forwardedUrl("/WEB-INF/jsp/user/view.jsp"))//验证视图渲染时forward到的jsp
.andExpect(status().isOk())//验证状态码
.andDo(print()); //输出MvcResult到控制台
测试 post 接口示例:
mockMvc.perform(post("/user").param("name", "zhang")) //执行传递参数的POST请求(也可以post("/user?name=zhang"))
.andExpect(handler().handlerType(UserController.class)) //验证执行的控制器类型
.andExpect(handler().methodName("create")) //验证执行的控制器方法名
.andExpect(model().hasNoErrors()) //验证页面没有错误
.andExpect(flash().attributeExists("success")) //验证存在flash属性
.andExpect(view().name("redirect:/user")); //验证视图
构造 h2 database 临时内存数据库
在启动配置 application.yaml 中添加相关配置。
spring:
datasource:
driver-class-name: "org.h2.Driver"
url: "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=MySQL;IGNORECASE=TRUE;"
h2:
console:
enabled: true
path: "/h2-console"
写 sql 构建需要用到的表和数据,drop_all.sql 用于清理数据库,schema.sql 用于建表,一些 "CHARACTER SET utf8mb4" 的 sql 表述是不能用的,insert_data.sql 用于插入数据。
// drop_all.sql
DROP ALL OBJECTS;
// schema.sql
CREATE TABLE mock_task
(
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键,自增 ID',
gmt_create datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
gmt_modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
is_finished tinyint(1) NOT NULL DEFAULT '0' COMMENT '任务是否执行完成',
type varchar(128) NOT NULL COMMENT '任务类型',
param text NOT NULL COMMENT '任务参数',
result text COMMENT '任务结果',
active_profile varchar(128) NOT NULL COMMENT '所属启动环境',
PRIMARY KEY (id),
KEY idx_is_finished0 (is_finished),
KEY idx_active_profile0 (active_profile)
);
// insert_data.sql
INSERT INTO mock_user (id, name)
VALUES
(1, '张三'),
(2, '李四')
;
把 h2 的配置导入和 sql 的执行写到配置类里,打上 @PostConstruct 注解启动执行。
@Configuration
@Profile("test")
@AutoConfigureBefore()
public class DbConfig {
@Autowired
private DataSource dataSource;
@Autowired
private WebApplicationContext webApplicationContext;
@PostConstruct
public void init() throws Exception {
Resource resource = webApplicationContext.getResource("classpath:db/drop_all.sql");
ScriptUtils.executeSqlScript(dataSource.getConnection(), resource);
resource = webApplicationContext.getResource("classpath:db/schema.sql");
ScriptUtils.executeSqlScript(dataSource.getConnection(), resource);
resource = webApplicationContext.getResource("classpath:db/insert_data.sql");
ScriptUtils.executeSqlScript(dataSource.getConnection(), resource);
}
}
如果是某个测试类特有的 mock 数据,可以在测试类前用注解 @sql 导入。
@Sql(
scripts = {DROP_ALL_SQL, SCHEMA_SQL, INSERT_DATA_SQL, TEST_SPECIFIC_INSERT_DATA_SQL},
executionPhase = BEFORE_TEST_METHOD
)
总结
综上,springboot + junit4 框架提供了一种集成测试方法,使用 mockmvc 构造 REST 请求,并使用 h2 拉起一个轻量级的 java 内存数据库,实现从接口层到数据层的访问测试。