zonkyio / embedded-database-spring-test

A library for creating isolated embedded databases for Spring-powered integration tests.
Apache License 2.0
412 stars 37 forks source link

Custom docker image with test data: How to use as a template #259

Closed jomatt closed 9 months ago

jomatt commented 9 months ago

This library seems to be exactly what I was looking for. I have a Spring Boot project with Flyway. In order to speed up integration tests, I want to make use of the templating feature that is mentioned in the README:

Note that the Docker provider is especially useful if you need to use some database extensions, or if you want to prepare a custom docker image containing some test data. In such cases, you can use any docker image compatible with the official docker images of the supported databases.

So I built a custom docker image including test data inside the postgres database. I also created a PostgreSQLContainerCustomizer bean to explicitly set the database name, assuming that the library would pick up this database name and use this DB as the template.

@Configuration
public class TestConfiguration {

    @Bean
    public PostgreSQLContainerCustomizer postgresContainerCustomizer() {
        return container -> container
                .withDatabaseName("postgres")
                .waitingFor(Wait.forLogMessage(".*database system is ready to accept connections.*", 1));
    }

}

However, I found out that the library always creates a clean database inside my custom docker image when I start my integration tests. This clean DB is then picked up by Flyway to run the migrations (instead of using the already populated and migrated postgres DB inside the custom docker image):

This is my Dockerfile that I use to build the custom image:

FROM timescale/timescaledb

RUN mkdir -p /var/lib/postgresql-static/data
ENV PGDATA /var/lib/postgresql-static/data

ENV DATA_DUMP /tmp/data_dump.sql
COPY data_dump.sql $DATA_DUMP
COPY init_db.sh /docker-entrypoint-initdb.d

and the init_db.sh file:

#!/bin/bash
set -e

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
    CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
EOSQL

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < "$DATA_DUMP"

Am I missing something? Any help would be greatly appreciated.

tomix26 commented 9 months ago

Hey @jomatt, you need to store your data in the template1 database. So, remove the .withDatabaseName("postgres") line from the container customizer and just change the target database in the init script to template1. Let me know if it works.

jomatt commented 9 months ago

@tomix26 thx for the swift answer - it works like a charm.

Another question: is there a way to programatically reset the DB without using the refresh property on the @AutoConfigureEmbeddedDatabase annotation? We have some tests that need to reset the state of the DB and others that don't. So it would be helpful to define for each test separately if the DB should be reset or not - instead of depending on a fixed refresh mode like AFTER_CLASS or AFTER_EACH_TEST_METHOD

tomix26 commented 9 months ago

If you are using Flyway, you can use the @FlywayTest annotation instead. It's possible to place the annotation even on a method level and the effect is the same as in case of the @AutoConfigureEmbeddedDatabase(refresh = ...) annotation, check out an example here.

Nevertheless, if you really want to control the reset programmatically, you can inject an embedded data source into your test class and use a lower-level API of the library to get a corresponding database context and reset it manually. See the snippet below.

@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase
public class DatabaseContextIntegrationTest {

    @Autowired
    private DataSource dataSource;

    @Test
    public void testMethod1() {
        // Potentially dirty database from previous tests
    }

    @Test
    public void testMethod2() {
        DatabaseContext databaseContext = AopProxyUtils.getDatabaseContext(dataSource);
        databaseContext.reset();

        // Fresh database ensured by manual reset
    }

    @Test
    public void testMethod3() {
        // Dirty database that contains data from the previous test
    }
}

Note that the order in which each test method is called can be non-deterministic, so the comments are just for illustrative purposes.

tomix26 commented 9 months ago

Ok, I guess it solved the problem. So, I'm closing the issue. Feel free to reopen it if you have more questions.