背景
现在自测大部分情况都是启动spring,通过http请求进行本地测试,依赖本地或者开发环境的数据库,不太方便,并且存在数据污染的问题。并且,如果我们修改了SQL,还需要重新进行一边所有测试,非常麻烦,所以支持单个Mapper的不依赖外部数据库的单测迫在眉睫!
特性
- 使用junit5
- 测试单个Mapper
- 不依赖Spring核心组件,无需启动Spring,执行速度飞快
- 不依赖外部MySQL数据库
代码示例,开箱可用
引入测试基类
- 基类初始化时需要XML路径、初始化SQL文件路径(一般是建表语句),可以添加TypeHandler等,具体参考下面的示例
- 由于H2的限制,部分数据类型不支持,可能需要对建表语句做一些微小的修改
SqlSession测试基类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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| public class BaseMybatisSqlSessionTest { private final List<String> xmlResourcePaths = new ArrayList<>(); private final List<String> initSqlResourcePaths = new ArrayList<>(); private final List<Class<? extends TypeHandler<?>>> typeHandlerClassList = new ArrayList<>(); protected SqlSession sqlSession;
@SneakyThrows @BeforeEach public void _baseInit() { sqlSession = initSqlSession(); executeInitSql(); }
@AfterEach public void _baseClear() { sqlSession.close(); }
@SneakyThrows private SqlSession initSqlSession() { String url = "jdbc:h2:mem:test;MODE=MySQL"; DataSource dataSource = new SimpleDriverDataSource(Driver.load(), url);
Configuration configuration = new Configuration(); Environment environment = new Environment("test", new JdbcTransactionFactory(), dataSource); configuration.setEnvironment(environment); TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); for (Class<?> typeHandlerClass : typeHandlerClassList) { typeHandlerRegistry.register(typeHandlerClass); } for (String xmlResourcePath : xmlResourcePaths) { ClassPathResource mapperResource = new ClassPathResource(xmlResourcePath); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperResource.getInputStream(), configuration, mapperResource.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); }
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(configuration);
return factory.openSession(); }
private void executeInitSql() { for (String path : initSqlResourcePaths) { executeSqlFile(path); } }
@SneakyThrows protected void executeSqlFile(String path) { ClassPathResource resource = new ClassPathResource(path); String sql = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8); executeSql(sql); }
@SneakyThrows protected void executeSql(String sql) { try (PreparedStatement statement = sqlSession.getConnection().prepareStatement(sql)) { statement.execute(); } }
protected void addXmlResourcePath(String path) { xmlResourcePaths.add(path); }
protected void addInitSqlResourcePath(String path) { initSqlResourcePaths.add(path); }
protected void addTypeHandlerClass(Class<? extends TypeHandler<?>> clazz) { typeHandlerClassList.add(clazz); }
}
|
对于单个Mapper的简单测试,提供一个基类,继承该基类可直接使用mapper
单个Mapper测试基类,继承自SqlSession测试类1 2 3 4 5 6 7 8 9 10 11
| @RequiredArgsConstructor public class BaseMybatisSingleMapperTest<Mapper> extends BaseMybatisSqlSessionTest { private final Class<Mapper> mapperClass; protected Mapper mapper;
@BeforeEach public void baseMybatisSingleMapperTestInit() { mapper = sqlSession.getMapper(mapperClass); }
}
|
单元测试类(业务测试类)
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 30 31 32 33 34 35
| public class EntityMapperTest extends BaseMybatisSingleMapperTest<EntityMapper> {
public EntityMapperTest() { super(EntityMapper.class); addXmlResourcePath("/entity-mapper.xml"); addInitSqlResourcePath("/schema.sql"); }
@Test public void test1() { insert("1");
Entity result = mapper.findByName("1");
assertNotNull(result); }
@Test public void test2() { insert("2");
Entity result = mapper.findByName("1");
assertNull(result); }
private void insert(String name) { Entity e = new Entity(); e.setName(name); mapper.insert(e); }
}
|
执行流程(每个Test)
- 执行
BaseMybatisSqlSessionTest的BeforeEach:启动H2内存数据库服务端。建立连接,解析XML,创建Mybatis的SqlSession - 执行
BaseMybatisSingleMapperTest的BeforeEach:从SqlSession里获取Mapper - 业务测试类(
EntityMapperTest)直接使用mapper进行单元测试 - 执行
BaseMybatisSqlSessionTest的AfterEach:关闭SqlSession。H2服务端在所有连接关闭后关闭内存数据库,清空数据,保证每个Test互不影响