Я тестирую TestContainers и хотел бы знать, как заполнить базу данных, выполнив файл .sql, чтобы создать структуру и добавить несколько строк.
Как это сделать?
@Rule
public PostgreSQLContainer postgres = new PostgreSQLContainer();
Привет, я использую эту библиотеку в среде Spring Boot
Вы можете найти это в документации: Использование сценария инициализации
Также есть статья, в которой упоминается использование метода PostgreSQLContainer::withInitScript: muzir.github.io/spring/testing/docker/testcontainers/postgre s /…
Фактически это JdbcDatabaseContainer::withInitScript, где JdbcDatabaseContainer является суперклассом PostgreSQLContainer, поэтому он должен работать не только для postgres, но и для других контейнеров.




Я считаю, что при использовании Spring Boot проще всего использовать поддержку URL-адресов JDBC в TestContainers.
Вы можете создать файл 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
Обратите внимание на часть :tc в URL-адресе JDBC.
Теперь вы можете написать такой модульный тест:
@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.
}
Примечание: это объясняется более подробно в главе 7 Практическое руководство по созданию серверной части API с помощью Spring Boot (отказ от ответственности: я являюсь автором книги)
Привет, Вим, спасибо за ответ. Интересный ответ, поэтому с TestContainers нет необходимости изобретать велосипед, поэтому в основном, если у нас есть база данных, нам нужно только продолжать использовать решения из Spring Boot. Завтра протестирую и проголосую :) Поздравляю со ссылкой на книгу. Завтра скачаю и пересмотрю. Приветствия из Брюсселя
Я использую это, но задаюсь вопросом, как настроить новый контейнер для каждого класса интеграции.
Среда 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
}
Вот документация.
Вы также можете взглянуть на 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-свободный DSL для заполнения вашей базы данных.
Как добавить такой SQL при работе со сложной базой данных и при необходимости поддерживать отношения внешних ключей?
Вы можете использовать База данных, который использует 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) //https://stackoverflow.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("[email protected]")).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("[email protected]", "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: "[email protected]"
NAME: "dbunit"
- ID: 2
EMAIL: "[email protected]"
NAME: "rmpestano"
- ID: 3
EMAIL: "[email protected]"
NAME: "springboot"
src/test/resources/datasets/expected_users.yml
users:
- ID: 1
EMAIL: "[email protected]"
NAME: "dbunit"
- ID: 3
EMAIL: "[email protected]"
NAME: "springboot"
src/test/resources/datasets/user.yml
users:
- ID: "regex:\\d+"
EMAIL: "[email protected]"
NAME: "new user"
После некоторых обзоров я думаю, что интересно рассмотреть примеры из Spring Data JDBC, которые используют тестовые контейнеры:
Примечание: Использовать Java 8
git clone https://github.com/spring-projects/spring-data-jdbc.git
mvn clean install -Pall-dbs
Я создам простой проект, добавив некоторые идеи о предыдущем упомянутом проекте.
Хуан Антонио
Есть еще один вариант, если вы определяете контейнер Postgres вручную без причудливых URL-адресов JDBC testcontainers, не связанных напрямую со 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 образа, если он уже был установлен
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");
}
@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 или R2DBCclass 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Он упоминается в официальной документации 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...
}
...
}
Вы используете только JUnit и TestContainers? Или другие фреймворки, например, Spring Boot?