"wix-embedded-mysql"使用本地内嵌Mysql进行springboot测试

1,773 阅读3分钟

测试的时候经常使用H2内存数据库@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)但是H2数据库不支持一些mysql的操作,往往需要修改一些sql才能跑通测试。

使用内嵌mysql就可以解决这个问题,用起来跟H2没区别(完全没感到启动速度有差别),但是完全支持mysql的所有功能,非常值得一试

除了spring默认的test依赖之外,需要的依赖是

<dependency>
    <groupId>com.wix</groupId>
    <artifactId>wix-embedded-mysql</artifactId>
    <version>4.6.1</version>
    <scope>test</scope>
</dependency>

因为使用内嵌数据需要执行数据库初始化sql,需要把sql打包进jar包中,添加一下pom设置:

<build>
    <resources>
        <resource>
            <!--sql的位置最好不要放在resources文件夹内-->
            <directory>src/main/sql</directory>
            <includes>
                <include>**/*.sql</include>
            </includes>
        </resource>
    </resources>
</build>

内嵌mysql数据库需要自己配置,在test包下创建EmbeddedMysqlServerConfig.java

image.png

参考wix 官方案例的配置如下:

import com.wix.mysql.EmbeddedMysql;
import com.wix.mysql.config.MysqldConfig;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;

import javax.sql.DataSource;
import java.io.IOException;
import java.net.ServerSocket;

import static com.wix.mysql.EmbeddedMysql.anEmbeddedMysql;
import static com.wix.mysql.config.Charset.UTF8;
import static com.wix.mysql.config.MysqldConfig.aMysqldConfig;
import static com.wix.mysql.distribution.Version.v5_6_latest;
import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_SINGLETON;

@TestConfiguration
public class EmbeddedMysqlServerConfig {
    private static final String DATABASE_USERNAME = "root_test";
    private static final String DATABASE_PASSWORD = "root_test";
    private static final String DATABASE_NAME = "test_db";

    @Bean
    @Scope(value = SCOPE_SINGLETON)
    public MysqldConfig config() throws IOException {
        return aMysqldConfig(v5_6_latest).withCharset(UTF8).withPort(getRandomFreePort())
                .withUser(DATABASE_USERNAME, DATABASE_PASSWORD).build();
    }

    @Bean(name = "embeddedMysql", destroyMethod = "stop")
    public EmbeddedMysql embeddedMysql(MysqldConfig config) {

        return anEmbeddedMysql(config)
                .addSchema(DATABASE_NAME)
                .start();
    }

    @Bean
    @Primary
    @DependsOn("embeddedMysql") // Because mySQLServer must be present before jdbc/hibernate
    public DataSource dataSource(MysqldConfig mysqldConfig) {
        return  DataSourceBuilder.create().username(mysqldConfig.getUsername()).password(mysqldConfig.getPassword())
                .url("jdbc:mysql://localhost:" + mysqldConfig.getPort() + "/" + DATABASE_NAME+"?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true")
                .driverClassName("com.mysql.cj.jdbc.Driver").build();
    }

    private int getRandomFreePort() throws IOException {
        // obtain random port for Mysql
        try (ServerSocket serverSocket = new ServerSocket(0)) {
            return serverSocket.getLocalPort();
        }
    }
}

用法

在test类上加上注解 👇
@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true", classes = EmbeddedMysqlServerConfig.class)

  • properties = "spring.main.allow-bean-definition-overriding=true"——test开启允许覆盖bean,将生产数据库源覆盖为内嵌mysql,如果test专属配置很多,也可以放在外部文件然后用@TestPropertySource("classpath:TestConfig.yml")启用

  • classes = EmbeddedMysqlServerConfig.class 启用test专用的configuration配置类,如果配置类是此测试类的静态内部类的话也可以在内部类加上@SpringBootConfiguration来替代

@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true", classes = EmbeddedMysqlServerConfig.class)
//Sql注解可以放置多个,用于在测试开始之前初始化数据库
@Sql(scripts = "classpath:数据库A测试数据.sql")
@Sql(scripts = "classpath:数据库B测试数据.sql")//maven默认会把资源拷贝进classpath根目录
class MySpringBootTest {
    //....
}

测试的一些其他注意点:

  • 方法上也可以用@Sql,但是如果需要和类上的@Sql一起生效需要添加注解 @SqlMergeMode(value = SqlMergeMode.MergeMode.MERGE)
@SqlMergeMode(value = SqlMergeMode.MergeMode.MERGE)
@Sql(statements = "update xxx set a = 1 where b = 3", executionPhase = BEFORE_TEST_METHOD)
@Sql(statements = "update xxx set a = 1 where b = 2", executionPhase = BEFORE_TEST_METHOD)
void test1() {
}
  • 如果需要指定test方法的执行顺序,需要在类上添加注解@TestMethodOrder(MethodOrderer.OrderAnnotation.class),方法上添加@org.junit.jupiter.api.Order(注意,这不是spring的@Order):
@Order(1)
void test1() {
}
@Order(2)
void test1() {
}
  • 测试中每个测试方法都是在独立的实例中(每个测试方法都会new一个该测试类)运行的,不共享类变量,如果想所有方法在同一个类实例中运行需要加类注解@TestInstance(TestInstance.Lifecycle.PER_CLASS)

防火墙通知

使用内嵌mysql的时候,每次都会弹出联网警告,win10关闭联网警告的方法如下:

Windows安全中心——>设置——>管理通知——> 关闭防火墙和网络保护通知 如果找不到按钮,可以win+R打开gpedit.msc
计算机配置——> 管理模板——> Windows 件——> Windows 安全中心 ——> 通知 ——> 隐藏所有通知 ——> 启用