在 Spring Boot 集成测试中使用 TestContainer 填充数据库
Posted
技术标签:
【中文标题】在 Spring Boot 集成测试中使用 TestContainer 填充数据库【英文标题】:Populate a database with TestContainers in a SpringBoot integration test 【发布时间】:2019-04-04 07:42:48 【问题描述】:我正在测试 TestContainers,我想知道如何填充执行 .sql 文件的数据库以创建结构并添加一些行。
怎么做?
@Rule
public PostgreSQLContainer postgres = new PostgreSQLContainer();
【问题讨论】:
您只使用 JUnit 和 TestContainers 吗?还是其他框架,例如 Spring Boot? 嗨,我在 Spring Boot 环境中使用这个库 你可以在文档中找到这个:Using an init script 还有一篇文章提到PostgreSQLContainer::withInitScript
方法的用法:muzir.github.io/spring/testing/docker/testcontainers/postgres/…
实际上是JdbcDatabaseContainer::withInitScript
,其中JdbcDatabaseContainer
是PostgreSQLContainer
的超类,因此它不仅适用于postgres,也适用于其他容器。
【参考方案1】:
最简单的方法是使用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();
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");
还有其他选择
Spring Test @Sql
注解
@SpringBootTest
@Sql(scripts = ["some/location/on/classpath/someScriptFirst.sql", "some/location/on/classpath/someScriptSecond.sql"])
public class SomeTest
//...
ResourceDatabasePopulator
来自jdbc.datasource.init
或r2dbc.connection.init
,当连续使用JDBC
或R2DBC
时
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
//...
使用 JDBC
时数据库 URI 中的初始化脚本
官方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...
...
【讨论】:
该解决方案很棒,但请注意它将使用 JDBC 驱动程序来执行语句。因此,例如,它可能不适用于 pgdump 输出。 ***.com/a/55460428/564005 之一将【参考方案2】:在使用 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
注意 JDBC url 中的 :tc
部分。
您现在可以像这样编写单元测试:
@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.
注意:这在Practical Guide to Building an API Back End with Spring Boot,第7章中有更详细的解释(免责声明:我是本书的作者)
【讨论】:
您好,Wim,感谢您的回复。有趣的答案,所以使用 TestContainers,没有必要重新发明***,所以基本上如果我们有数据库,那么我们只需要继续使用 Spring Boot 的解决方案。我明天会测试,我会给你投票:) 恭喜这本书的参考。我明天会下载,我会审查。来自布鲁塞尔的欢呼声 我正在使用它,但想知道如何为每个集成类配置一个新容器。【参考方案3】:还有一个选项,如果你手动定义 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)
此外,如果您在运行时需要某些东西,您可以随时执行
pgDb.execInContainer("psql ....")
.
【讨论】:
.withClasspathResourceMapping("init-postgres.sql", "/docker-entrypoint-initdb.d/init-postgres.sql", BindMode.READ_ONLY) 为我工作 @CHEM_Eugene 您能否在您发布的代码中添加文件示例?不确定"/docker-entrypoint-initdb.d/init-postgres.sql"
应该来自哪里?
这可能会覆盖图像的/docker-entrypoint-initdb.d
,如果它已经设置了【参考方案4】:
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
这是documentation。
您还可以查看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
另外,您可以查看DbSetup,它提供了一个 java fluent DSL 来填充您的数据库。
【讨论】:
在处理复杂的数据库需要维护外键关系时,如何添加这样的SQL?【参考方案5】:您可以使用DatabaseRider(在后台使用DBUnit)填充测试数据库,并使用TestContainers 作为测试数据源。以下是一个示例测试,完整的源代码在github here。
@RunWith(SpringRunner.class)
@SpringBootTest
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @ActiveProfiles("integration-test")
@DBRider //enables database rider in spring tests
@DBUnit(caseInsensitiveStrategy = Orthography.LOWERCASE) //https://***.com/questions/43111996/why-postgresql-does-not-like-uppercase-table-names
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
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
最后是数据集:
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"
src/test/resources/datasets/expected_users.yml
users:
- ID: 1
EMAIL: "dbunit@gmail.com"
NAME: "dbunit"
- ID: 3
EMAIL: "springboot@gmail.com"
NAME: "springboot"
src/test/resources/datasets/user.yml
users:
- ID: "regex:\\d+"
EMAIL: "newUser@gmail.com"
NAME: "new user"
【讨论】:
【参考方案6】:经过一些评论,我认为回顾 Spring Data JDBC 中使用测试容器的示例很有趣:
注意:使用 Java 8
git clone https://github.com/spring-projects/spring-data-jdbc.git
mvn clean install -Pall-dbs
我将创建一个简单的项目,添加一些关于之前引用的项目的想法。
胡安·安东尼奥
【讨论】:
以上是关于在 Spring Boot 集成测试中使用 TestContainer 填充数据库的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Spring Boot 集成测试中使用嵌入式 MongoDB
@WithMockUser 在集成测试中不起作用 - Spring boot