集成测试 springboot + junit4 + mockmvc + h2

1,477 阅读3分钟

引言

相比于单元测试,集成测试能够跨模块执行完整功能,从接口层开始调用到底层对数据库的访问,用更少的 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 内存数据库,实现从接口层到数据层的访问测试。