在 SpringBoot 集成测试中使用 TestContainers 填充数据库

jab*_*ena 12 spring-boot testcontainers

我正在测试 TestContainers,我想知道如何填充执行 .sql 文件的数据库以创建结构并添加一些行。

怎么做?

@Rule
public PostgreSQLContainer postgres = new PostgreSQLContainer();
Run Code Online (Sandbox Code Playgroud)

vic*_*let 8

Spring 框架提供了为测试套件或测试单元执行 SQL 脚本的能力。例如:

@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"}) 
public void userTest {
   // execute code that relies on the test schema and test data
}
Run Code Online (Sandbox Code Playgroud)

这是文档

您还可以查看Spring Test DBUnit,它提供注释来填充测试单元的数据库。它使用 XML 数据集文件。

@Test
@DatabaseSetup(value = "insert.xml")
@DatabaseTearDown(value = "insert.xml")
public void testInsert() throws Exception {
     // Inserts "insert.xml" before test execution
     // Remove "insert.xml" after test execution
}
Run Code Online (Sandbox Code Playgroud)

另外,您可以查看DbSetup,它提供了一个 Java 流畅的 DSL 来填充您的数据库。


Wim*_*uwe 7

在使用 Spring Boot 时,我发现使用 TestContainers 的 JDBC URL 支持最容易。

您可以创建一个application-integration-test.properties文件(通常src/test/resources是这样的:

spring.datasource.url=jdbc:tc:postgresql://localhost/myappdb
spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.username=user
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=none
# This line is only needed if you are using flyway for database migrations
# and not using the default location of `db/migration`
spring.flyway.locations=classpath:db/migration/postgresql
Run Code Online (Sandbox Code Playgroud)

请注意:tcJDBC url 中的部分。

您现在可以像这样编写单元测试:

@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @ActiveProfiles("integration-test")
public class UserRepositoryIntegrationTest {
      @Autowired
      private MyObjectRepository repository;
      @PersistenceContext
      private EntityManager entityManager;
      @Autowired
      private JdbcTemplate template;

@Test
public void test() {
  // use your Spring Data repository, or the EntityManager or the JdbcTemplate to run your SQL and populate your database.
}
Run Code Online (Sandbox Code Playgroud)

注意:这在使用 Spring Boot 构建 API 后端的实用指南,第 7 章中有更详细的解释(免责声明:我是这本书的作者)

  • 嗨,维姆,感谢您的回复。有趣的答案,所以使用 TestContainers,没有必要重新发明轮子,所以基本上如果我们有数据库,那么我们只需要继续使用 Spring Boot 的解决方案。我明天会测试,我会给你投票:) 恭喜你获得了参考书目。我明天下载,我会审查。来自布鲁塞尔的欢呼 (2认同)

luk*_*uke 6

最简单的方法是使用 JdbcDatabaseContainer::withInitScript

这种解决方案的优点是脚本在Spring Application Context加载之前运行(至少当它在静态块中时)并且代码非常简单。

例子:

static {
    postgreSQLContainer = new PostgreSQLContainer("postgres:9.6.8")
            .withDatabaseName("integration-tests-db")
            .withUsername("sa")
            .withPassword("sa");
    postgreSQLContainer
            .withInitScript("some/location/on/classpath/someScript.sql");
    postgreSQLContainer.start();
}
Run Code Online (Sandbox Code Playgroud)

JdbcDatabaseContainer是超类,PostgreSQLContainer因此该解决方案不仅适用于postgres,还适用于其他容器。

如果你想运行多个脚本,你可以用类似的方式来完成

例子:

static {
    postgreSQLContainer = new PostgreSQLContainer("postgres:9.6.8")
            .withDatabaseName("integration-tests-db")
            .withUsername("sa")
            .withPassword("sa");
    postgreSQLContainer.start();

    var containerDelegate = new JdbcDatabaseDelegate(postgreSQLContainer, "");

     ScriptUtils.runInitScript(containerDelegate, "some/location/on/classpath/someScriptFirst.sql");
     ScriptUtils.runInitScript(containerDelegate, "some/location/on/classpath/someScriptSecond.sql");
     ScriptUtils.runInitScript(containerDelegate, "ssome/location/on/classpath/someScriptThird.sql");
}
Run Code Online (Sandbox Code Playgroud)

还有其他选择

Spring 测试@Sql注解

@SpringBootTest
@Sql(scripts = ["some/location/on/classpath/someScriptFirst.sql", "some/location/on/classpath/someScriptSecond.sql"])
public class SomeTest {
    //...
}
Run Code Online (Sandbox Code Playgroud)

ResourceDatabasePopulatorjdbc.datasource.initr2dbc.connection.init当使用JDBCR2DBC连续

class DbInitializer {
    private static boolean initialized = false;

    @Autowired
    void initializeDb(ConnectionFactory connectionFactory) {
        if (!initialized) {
            ResourceLoader resourceLoader = new DefaultResourceLoader();
            Resource[] scripts = new Resource[] {
                    resourceLoader.getResource("classpath:some/location/on/classpath/someScriptFirst.sql"),
                    resourceLoader.getResource("classpath:some/location/on/classpath/someScriptSecond.sql"),
                    resourceLoader.getResource("classpath:some/location/on/classpath/someScriptThird.sql")
            };
            new ResourceDatabasePopulator(scripts).populate(connectionFactory).block();
            initialized = true;
        }
    }
}

@SpringBootTest
@Import(DbInitializer.class)
public class SomeTest {
    //...
}
Run Code Online (Sandbox Code Playgroud)

使用时数据库 URI 中的初始化脚本 JDBC

它在官方Testcontainers文档中提到:https :
//www.testcontainers.org/modules/databases/jdbc/

类路径文件:
jdbc:tc:postgresql:9.6.8:///databasename?TC_INITSCRIPT=somepath/init_mysql.sql

不在类路径上的文件,但其路径相对于工作目录,通常是项目根目录:
jdbc:tc:postgresql:9.6.8:///databasename?TC_INITSCRIPT=file:src/main/resources/init_mysql.sql

使用初始化函数:
jdbc:tc:postgresql:9.6.8:///databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction

package org.testcontainers.jdbc;

public class JDBCDriverTest {
    public static void sampleInitFunction(Connection connection) throws SQLException {
        // e.g. run schema setup or Flyway/liquibase/etc DB migrations here...
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)


rmp*_*ano 5

您可以使用DatabaseRider,它在后台使用DBUnit来填充测试数据库和TestContainers作为测试数据源。以下是一个示例测试,完整的源代码可以在github上找到。

@RunWith(SpringRunner.class)
@SpringBootTest
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @ActiveProfiles("integration-test")
@DBRider //enables database rider in spring tests 
@DBUnit(caseInsensitiveStrategy = Orthography.LOWERCASE) ///sf/ask/3017839751/
public class SpringBootDBUnitIt {

    private static final PostgreSQLContainer postgres = new PostgreSQLContainer(); //creates the database for all tests on this file 

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private UserRepository userRepository;


    @BeforeClass
    public static void setupContainer() {
        postgres.start();
    }

    @AfterClass
    public static void shutdown() {
        postgres.stop();
    }


    @Test
    @DataSet("users.yml")
    public void shouldListUsers() throws Exception {
        assertThat(userRepository).isNotNull();
        assertThat(userRepository.count()).isEqualTo(3);
        assertThat(userRepository.findByEmail("springboot@gmail.com")).isEqualTo(new User(3));
    }

    @Test
    @DataSet("users.yml") //users table will be cleaned before the test because default seeding strategy
    @ExpectedDataSet("expected_users.yml")
    public void shouldDeleteUser() throws Exception {
        assertThat(userRepository).isNotNull();
        assertThat(userRepository.count()).isEqualTo(3);
        userRepository.delete(userRepository.findOne(2L));
        entityManager.flush();//can't SpringBoot autoconfigure flushmode as commit/always
        //assertThat(userRepository.count()).isEqualTo(2); //assertion is made by @ExpectedDataset
    }

    @Test
    @DataSet(cleanBefore = true)//as we didn't declared a dataset DBUnit wont clear the table
    @ExpectedDataSet("user.yml")
    public void shouldInsertUser() throws Exception {
        assertThat(userRepository).isNotNull();
        assertThat(userRepository.count()).isEqualTo(0);
        userRepository.save(new User("newUser@gmail.com", "new user"));
        entityManager.flush();//can't SpringBoot autoconfigure flushmode as commit/always
        //assertThat(userRepository.count()).isEqualTo(1); //assertion is made by @ExpectedDataset
    }

}
Run Code Online (Sandbox Code Playgroud)

src/test/resources/application-integration-test.properties

spring.datasource.url=jdbc:tc:postgresql://localhost/test
spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.username=test
spring.datasource.password=test
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
#spring.jpa.properties.org.hibernate.flushMode=ALWAYS #doesn't take effect 
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
Run Code Online (Sandbox Code Playgroud)

最后是数据集:

src/test/resources/datasets/users.yml

users:
  - ID: 1
    EMAIL: "dbunit@gmail.com"
    NAME: "dbunit"
  - ID: 2
    EMAIL: "rmpestano@gmail.com"
    NAME: "rmpestano"
  - ID: 3
    EMAIL: "springboot@gmail.com"
    NAME: "springboot"
Run Code Online (Sandbox Code Playgroud)

src/test/resources/datasets/expected_users.yml

users:
  - ID: 1
    EMAIL: "dbunit@gmail.com"
    NAME: "dbunit"
  - ID: 3
    EMAIL: "springboot@gmail.com"
    NAME: "springboot"
Run Code Online (Sandbox Code Playgroud)

src/test/resources/datasets/user.yml

users:
  - ID: "regex:\\d+"
    EMAIL: "newUser@gmail.com"
    NAME: "new user"
Run Code Online (Sandbox Code Playgroud)


gle*_*sts 5

还有一种选择,如果您手动定义 Postgres 容器而没有花哨的 testcontainers JDBC url 内容,则与 Spring 不直接相关。Postgres 镜像允许将包含 sql 脚本的目录链接到容器卷并自动执行它们。

GenericContainer pgDb = new PostgreSQLContainer("postgres:9.4-alpine")
  .withFileSystemBind("migrations/sqls", "/docker-entrypoint-initdb.d",
    BindMode.READ_ONLY)
Run Code Online (Sandbox Code Playgroud)

此外,如果您在运行时需要某些东西,您可以随时执行 pgDb.execInContainer("psql ....").

  • .withClasspathResourceMapping("init-postgres.sql", "/docker-entrypoint-initdb.d/init-postgres.sql", BindMode.READ_ONLY) 为我工作 (2认同)