Заполните базу данных TestContainers в тесте интеграции SpringBoot

Я тестирую TestContainers и хотел бы знать, как заполнить базу данных, выполнив файл .sql, чтобы создать структуру и добавить несколько строк.

Как это сделать?

@Rule
public PostgreSQLContainer postgres = new PostgreSQLContainer();

Вы используете только JUnit и TestContainers? Или другие фреймворки, например, Spring Boot?

Wim Deblauwe 31.10.2018 08:59

Привет, я использую эту библиотеку в среде Spring Boot

jabrena 31.10.2018 16:33

Вы можете найти это в документации: Использование сценария инициализации

Guillaume Husta 07.01.2019 23:49

Также есть статья, в которой упоминается использование метода PostgreSQLContainer::withInitScript: muzir.github.io/spring/testing/docker/testcontainers/postgre‌ s /…

luke 02.01.2021 19:14

Фактически это JdbcDatabaseContainer::withInitScript, где JdbcDatabaseContainer является суперклассом PostgreSQLContainer, поэтому он должен работать не только для postgres, но и для других контейнеров.

luke 02.01.2021 19:53
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
12
5
15 437
6

Ответы 6

Я считаю, что при использовании 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. Завтра протестирую и проголосую :) Поздравляю со ссылкой на книгу. Завтра скачаю и пересмотрю. Приветствия из Брюсселя

jabrena 31.10.2018 21:58

Я использую это, но задаюсь вопросом, как настроить новый контейнер для каждого класса интеграции.

Jibin TJ 08.11.2018 16:22

Среда 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 при работе со сложной базой данных и при необходимости поддерживать отношения внешних ключей?

Arefe 14.01.2021 12:34

Вы можете использовать База данных, который использует 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 23.05.2020 11:01

@CHEM_Eugene Можете ли вы добавить пример файлов в опубликованный вами код? Не знаете, откуда взять "/docker-entrypoint-initdb.d/init-postgres.sql"?

Dmytro Chasovskyi 18.06.2020 10:03

Это могло бы переопределить /docker-entrypoint-initdb.d образа, если он уже был установлен

Vladyslav Nikolaiev 01.09.2020 10:14

Самый простой способ - использовать 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 {
    //...
}

Сценарий инициализации в 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...
    }
    ...
}

Другие вопросы по теме