Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Populate a database with TestContainers in a SpringBoot integration test

I am testing TestContainers and I would like to know how to populate a database executing a .sql file to create the structure and add some rows.

How to do it?

@Rule
public PostgreSQLContainer postgres = new PostgreSQLContainer();
like image 614
jabrena Avatar asked Oct 31 '18 07:10

jabrena


People also ask

How to disable @datajpatest -tests in Spring Boot?

If it is a static field, the container will be started before the first test of the class and stopped after the last one. Normally, Spring Boot will start an in-memory database for @DataJpaTest -Tests. We use AutoConfigureTestDatabase to explicitly deactivate this default behavior.

How can I populate a database for a test in spring?

You can also take a look at Spring Test DBUnit which provides annotations to populate your database for a test unit. It uses XML dataset files. Also, you can take a look at DbSetup, which provides a java fluent DSL to populate your database.

What database should I use for test classes in Spring Boot?

Besides spring-boot-starter-test we need org.testcontainers:junit-jupiter as well as an org.testcontainers reference to the database we have in use. In our case we choose MySQL. To centralize the recurring logic of our test classes, we first create an abstract base class from which our IT classes then inherit.

How do I use PostgreSQL with Spring test containers?

To use the PostgreSQL database in our tests, we have to add the Testcontainers dependency with test scope and the PostgreSQL driver to our pom.xml: Let's also create an application.properties file under the test resources directory in which we instruct Spring to use the proper driver class and to create and drop the scheme at each test run:


2 Answers

The easiest way is to use JdbcDatabaseContainer::withInitScript

Advantage of this solution is that script is run before Spring Application Context loads (at least when it is in a static block) and the code is quite simple.

Example:

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 is superclass of PostgreSQLContainer so this solution should work not only for postgres, but also for other containers.

If you want to run multiple scripts you can do it in a similar manner

Example:

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");
}

There are also other options

Spring Test @Sql annotation

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

ResourceDatabasePopulator from jdbc.datasource.init or r2dbc.connection.init when using JDBC or R2DBC consecutively

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 {
    //...
}

Init script in database URI when using JDBC

It is mentioned in offical Testcontainers documentation:
https://www.testcontainers.org/modules/databases/jdbc/

Classpath file:
jdbc:tc:postgresql:9.6.8:///databasename?TC_INITSCRIPT=somepath/init_mysql.sql

File that is not on classpath, but its path is relative to the working directory, which will usually be the project root:
jdbc:tc:postgresql:9.6.8:///databasename?TC_INITSCRIPT=file:src/main/resources/init_mysql.sql

Using an init function:
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...
    }
    ...
}
like image 199
luke Avatar answered Sep 27 '22 16:09

luke


Spring framework provides the ability to execute SQL scripts for test suites or for a test unit. For example:

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

Here's the documentation.

You can also take a look at Spring Test DBUnit which provides annotations to populate your database for a test unit. It uses XML dataset files.

@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
}

Also, you can take a look at DbSetup, which provides a java fluent DSL to populate your database.

like image 28
victor gallet Avatar answered Sep 27 '22 17:09

victor gallet