Embedded Database
Introduction
The primary goal of this project is to make it easier to write Spring-powered integration tests that rely on PostgreSQL database. This library is responsible for creating and managing isolated embedded databases for each test class or test method, based on test configuration.
Features
- Supports both
Spring
andSpring Boot
frameworks- Supported versions are Spring 4.3.0+ and Spring Boot 1.4.0+
- Automatic integration with Spring TestContext framework
- Context caching is fully supported
- Seamless integration with Flyway database migration tool
- Just place
@FlywayTest
annotation on test class or method
- Just place
- Optimized initialization and cleaning of embedded databases
- Database templates are used to reduce the loading time
- Uses lightweight bundles of PostgreSQL binaries with reduced size
- Providing configurable version of PostgreSQL binaries
- Providing PostgreSQL 11+ binaries even for Linux platform
- Support for running inside Docker, including Alpine Linux
Quick Start
Maven Configuration
Add the following Maven dependency:
<dependency>
<groupId>io.zonky.test</groupId>
<artifactId>embedded-database-spring-test</artifactId>
<version>1.4.1</version>
<scope>test</scope>
</dependency>
The default version of the embedded database is PostgreSQL 10.7
, but you can change it by following the instructions described in Changing the version of postgres binaries.
Basic Usage
The configuration of the embedded database is driven by @AutoConfigureEmbeddedDatabase
annotation. Just place the annotation on a test class and that's it! The existing data source will be replaced by the testing one, or a new data source will be created.
Examples
Creating a new empty database with a specified bean name
A new data source with a specified name will be created and injected into all related components. You can also inject it into test class as shown below.
@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase(beanName = "dataSource")
public class EmptyDatabaseIntegrationTest {
@Autowired
private DataSource dataSource;
// class body...
}
Replacing an existing data source with an empty database
In case the test class uses a spring context that already contains a data source bean, the data source bean will be automatically replaced by a testing data source. Please note that if the context contains multiple data sources the bean name must be specified by @AutoConfigureEmbeddedDatabase(beanName = "dataSource")
to identify the data source that will be replaced. The newly created data source bean will be injected into all related components and you can also inject it into test class.
@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase
@ContextConfiguration("path/to/application-config.xml")
public class EmptyDatabaseIntegrationTest {
// class body...
}
Creating multiple databases within a single test class
The @AutoConfigureEmbeddedDatabase
is a repeatable annotation, so you can annotate a test class with multiple annotations to create multiple independent databases.
Each of them may have completely different configuration parameters, including the database provider as demonstrated in the example below.
Note that if multiple annotations on a single class are applied, some optimization techniques can not be used and database initialization may be slower.
@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase(beanName = "dataSource1")
@AutoConfigureEmbeddedDatabase(beanName = "dataSource2")
@AutoConfigureEmbeddedDatabase(beanName = "dataSource3", provider = DOCKER)
public class MultipleDatabasesIntegrationTest {
@Autowired
private DataSource dataSource1;
@Autowired
private DataSource dataSource2;
@Autowired
private DataSource dataSource3;
// class body...
}
@FlywayTest
annotation on a test class
Using The library supports the use of @FlywayTest
annotation. If you use it, the embedded database will be automatically initialized and cleaned by Flyway database migration tool. If you don't specify any custom migration locations the default path db/migration
will be applied.
Note that if you place the annotation on a class, all tests within the class share the same database. If you want all the tests to be isolated, you need to put the @FlywayTest
annotation on each test method separately.
@RunWith(SpringRunner.class)
@FlywayTest
@AutoConfigureEmbeddedDatabase
@ContextConfiguration("path/to/application-config.xml")
public class FlywayMigrationIntegrationTest {
// class body...
}
@FlywayTest
annotation on a test method
Using It is also possible to use @FlywayTest
annotation on a test method. In such case, the isolated embedded database will be created and managed for the duration of the test method. If you don't specify any custom migration locations the default path db/migration
will be applied.
@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase
@ContextConfiguration("path/to/application-config.xml")
public class FlywayMigrationIntegrationTest {
@Test
@FlywayTest(locationsForMigrate = "test/db/migration")
public void testMethod() {
// method body...
}
}
@FlywayTest
annotation with additional options
Using In case you want to apply migrations from some additional locations, you can use @FlywayTest(locationsForMigrate = "path/to/migrations")
configuration. In that case, the sql scripts from the default location and also sql scripts from the additional locations will be applied. If you need to prevent the loading of the scripts from the default location you can use @FlywayTest(overrideLocations = true, ...)
annotation configuration.
See Usage of Annotation FlywayTest for more information about configuration options of @FlywayTest
annotation.
@RunWith(SpringRunner.class)
@FlywayTest(locationsForMigrate = "test/db/migration")
@AutoConfigureEmbeddedDatabase
@ContextConfiguration("path/to/application-config.xml")
public class FlywayMigrationIntegrationTest {
// class body...
}
@DataJpaTest
or @JdbcTest
annotation
Using Spring Boot provides several annotations to simplify writing integration tests.
One of them is the @DataJpaTest
annotation, which can be used when a test focuses only on JPA components.
By default, tests annotated with this annotation use an in-memory database.
But if the @DataJpaTest
annotation is used together with the @AutoConfigureEmbeddedDatabase
annotation,
the in-memory database is automatically disabled and replaced by the embedded postgres database.
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureEmbeddedDatabase
public class SpringDataJpaAnnotationTest {
// class body...
}
You can also consider creating a custom composed annotation.
Example of composed annotation
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@DataJpaTest
@AutoConfigureEmbeddedDatabase
public @interface PostgresDataJpaTest {
@AliasFor(annotation = DataJpaTest.class)
boolean showSql() default true;
@AliasFor(annotation = DataJpaTest.class)
boolean useDefaultFilters() default true;
@AliasFor(annotation = DataJpaTest.class)
Filter[] includeFilters() default {};
@AliasFor(annotation = DataJpaTest.class)
Filter[] excludeFilters() default {};
@AliasFor(annotation = DataJpaTest.class)
Class<?>[] excludeAutoConfiguration() default {};
}
Advanced Topics
Database Providers
The library can be combined with different database providers. Each of them has its advantages and disadvantages summarized in the table below.
Docker provides the greatest flexibility, but it can be slightly slower than the native versions. However, the change of database providers is really easy, so you can try them all.
You can either configure a provider for each class separately by @AutoConfigureEmbeddedDatabase(provider = ...)
annotation,
or through zonky.test.database.provider
property globally.
Docker | Zonky | OpenTable | Yandex | |
---|---|---|---|---|
Startup Time | Slightly slower | Fast | Fast | Slow, depends on platform |
Performance | Slightly slower, depends on platform | Native | Native | Native |
Supported Platforms | All supported by Docker | Mac OS, Windows, Linux, Alpine Linux | Mac OS, Windows, Linux | Mac OS, Windows, Linux |
Supported Architectures | Based on image | amd64, i386, arm32v6, arm32v7, arm64v8, ppc64le | amd64 | amd64 |
Configurable Postgres Version | Yes, at runtime | Yes, at compile time | No | Yes, at runtime |
Alpine Linux Support | Yes | Yes | No | No |
Extension Support | Yes | No | No | No |
In-Memory Support | Yes | No | No | No |
Common Configuration
The @AutoConfigureEmbeddedDatabase
annotation can be used for some basic configuration, advanced configuration requires properties or yaml files.
The following configuration keys are used by all providers:
zonky.test.database.provider=zonky # Provider used to create the underlying embedded database, see the documentation for the comparision matrix.
zonky.test.database.postgres.client.properties.*= # Additional properties used to configure the test data source.
zonky.test.database.postgres.initdb.properties.*= # Additional properties to pass to initdb command during the database initialization.
zonky.test.database.postgres.server.properties.*= # Additional properties used to configure the embedded PostgreSQL server.
Note that the library includes configuration metadata that offer contextual help and code completion as users are working with Spring Boot's application.properties
or application.yml
files.
Example configuration:
zonky.test.database.postgres.client.properties.stringtype=unspecified
zonky.test.database.postgres.initdb.properties.lc-collate=cs_CZ.UTF-8
zonky.test.database.postgres.initdb.properties.lc-monetary=cs_CZ.UTF-8
zonky.test.database.postgres.initdb.properties.lc-numeric=cs_CZ.UTF-8
zonky.test.database.postgres.server.properties.shared_buffers=512MB
zonky.test.database.postgres.server.properties.max_connections=100
Using Zonky Provider (default)
This is the default provider, so you do not have to do anything special,
just use the @AutoConfigureEmbeddedDatabase
annotation in its basic form without any provider.
@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase
public class DefaultProviderIntegrationTest {
// class body...
}
Changing the version of postgres binaries
The version of the binaries can be managed by importing embedded-postgres-binaries-bom
in a required version into your dependency management section.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.zonky.test.postgres</groupId>
<artifactId>embedded-postgres-binaries-bom</artifactId>
<version>11.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Using Maven BOMs in Gradle
In Gradle, there are several ways how to import a Maven BOM.
-
You can define a resolution strategy to check and change the version of transitive dependencies manually:
configurations.all { resolutionStrategy.eachDependency { DependencyResolveDetails details -> if (details.requested.group == 'io.zonky.test.postgres') { details.useVersion '11.1.0' } } }
-
If you use Gradle 5+, Maven BOMs are supported out of the box, so you can import the bom:
dependencies { implementation enforcedPlatform('io.zonky.test.postgres:embedded-postgres-binaries-bom:11.1.0') }
-
Or, you can use Spring's dependency management plugin that provides Maven-like dependency management to Gradle:
plugins { id "io.spring.dependency-management" version "1.0.6.RELEASE" } dependencyManagement { imports { mavenBom 'io.zonky.test.postgres:embedded-postgres-binaries-bom:11.1.0' } }
A list of all available versions of postgres binaries is here: https://mvnrepository.com/artifact/io.zonky.test.postgres/embedded-postgres-binaries-bom
Note that the release cycle of the postgres binaries is independent of the release cycle of this library, so you can upgrade to a new version of postgres binaries immediately after it is released.
Enabling support for additional architectures
By default, only the support for amd64
architecture is enabled.
Support for other architectures can be enabled by adding the corresponding Maven dependencies as shown in the example below.
<dependency>
<groupId>io.zonky.test.postgres</groupId>
<artifactId>embedded-postgres-binaries-linux-i386</artifactId>
<scope>test</scope>
</dependency>
Supported platforms: Darwin
, Windows
, Linux
, Alpine Linux
Supported architectures: amd64
, i386
, arm32v6
, arm32v7
, arm64v8
, ppc64le
Note that not all architectures are supported by all platforms, look here for an exhaustive list of all available artifacts: https://mvnrepository.com/artifact/io.zonky.test.postgres
Since PostgreSQL 10.0
, there are additional artifacts with alpine-lite
suffix. These artifacts contain postgres binaries for Alpine Linux with disabled ICU support for further size reduction.
Zonky-specific provider configuration
The provider configuration can be customized with bean implementing Consumer<EmbeddedPostgres.Builder>
interface.
The obtained builder provides methods to change the configuration before the database is started.
import io.zonky.test.db.postgres.embedded.EmbeddedPostgres;
@Configuration
public class EmbeddedPostgresConfiguration {
@Bean
public Consumer<EmbeddedPostgres.Builder> embeddedPostgresCustomizer() {
return builder -> builder.setPGStartupWait(Duration.ofSeconds(60L));
}
}
@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase
@ContextConfiguration(classes = EmbeddedPostgresConfiguration.class)
public class EmbeddedPostgresIntegrationTest {
// class body...
}
Using Docker Provider
Docker provider is especially useful if you need some PostgreSQL extension. You can use any docker image that is compatible with the official Postgres image.
Before you use Docker provider, you must add the following Maven dependency:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.10.6</version>
<scope>test</scope>
</dependency>
Then, you can use @AutoConfigureEmbeddedDatabase
annotation to set up the DatabaseProvider.DOCKER
provider.
@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase(provider = DOCKER)
public class DockerProviderIntegrationTest {
// class body...
}
Docker-specific provider configuration
The provider configuration can be controlled by properties in the zonky.test.database.postgres.docker
group.
zonky.test.database.postgres.docker.image=postgres:10.7-alpine # Docker image containing PostgreSQL database.
zonky.test.database.postgres.docker.tmpfs.enabled=false # Whether to mount postgres data directory as tmpfs.
zonky.test.database.postgres.docker.tmpfs.options=rw,noexec,nosuid # Mount options used to configure the tmpfs filesystem.
Using OpenTable Provider
Before you use OpenTable provider, you must add the following Maven dependency:
<dependency>
<groupId>com.opentable.components</groupId>
<artifactId>otj-pg-embedded</artifactId>
<version>0.13.1</version>
<scope>test</scope>
</dependency>
Then, you can use @AutoConfigureEmbeddedDatabase
annotation to set up the DatabaseProvider.OPENTABLE
provider.
@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase(provider = OPENTABLE)
public class OpenTableProviderIntegrationTest {
// class body...
}
OpenTable-specific provider configuration
The provider configuration can be customized with bean implementing Consumer<EmbeddedPostgres.Builder>
interface.
The obtained builder provides methods to change the configuration before the database is started.
import com.opentable.db.postgres.embedded.EmbeddedPostgres;
@Configuration
public class EmbeddedPostgresConfiguration {
@Bean
public Consumer<EmbeddedPostgres.Builder> embeddedPostgresCustomizer() {
return builder -> builder.setPGStartupWait(Duration.ofSeconds(60L));
}
}
@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase(provider = OPENTABLE)
@ContextConfiguration(classes = EmbeddedPostgresConfiguration.class)
public class EmbeddedPostgresIntegrationTest {
// class body...
}
Using Yandex Provider
Before you use Yandex provider, you must add the following Maven dependency:
<dependency>
<groupId>ru.yandex.qatools.embed</groupId>
<artifactId>postgresql-embedded</artifactId>
<version>2.10</version>
<scope>test</scope>
</dependency>
Then, you can use @AutoConfigureEmbeddedDatabase
annotation to set up the DatabaseProvider.YANDEX
provider.
@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase(provider = YANDEX)
public class YandexProviderIntegrationTest {
// class body...
}
Yandex-specific provider configuration
The provider configuration can be controlled by properties in the zonky.test.database.postgres.yandex-provider
group.
zonky.test.database.postgres.yandex-provider.postgres-version=10.7-1 # Version of EnterpriseDB PostgreSQL binaries (https://www.enterprisedb.com/download-postgresql-binaries).
Database Prefetching
Database prefetching is used to speed up the database initialization. It can be customized by properties in the zonky.test.database.prefetching
group.
zonky.test.database.prefetching.thread-name-prefix=prefetching- # Prefix to use for the names of database prefetching threads.
zonky.test.database.prefetching.concurrency=3 # Maximum number of concurrently running database prefetching threads.
zonky.test.database.prefetching.pipeline-cache-size=3 # Maximum number of prepared databases per pipeline.
Disabling auto-configuration
By default, the library automatically registers all necessary context customizers and test execution listeners.
If this behavior is inappropriate for some reason, you can deactivate it by exclusion of the embedded-database-spring-test-autoconfigure
dependency.
<dependency>
<groupId>io.zonky.test</groupId>
<artifactId>embedded-database-spring-test</artifactId>
<version>1.4.1</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>io.zonky.test</groupId>
<artifactId>embedded-database-spring-test-autoconfigure</artifactId>
</exclusion>
</exclusions>
</dependency>
Background bootstrapping mode
Using this feature causes that the initialization of the data source and the execution of Flyway database migrations are performed in background bootstrap mode.
In such case, a DataSource
proxy is immediately returned for injection purposes instead of waiting for the Flyway's bootstrapping to complete.
However, note that the first actual call to a data source method will then block until the Flyway's bootstrapping completed, if not ready by then.
For maximum benefit, make sure to avoid early data source calls in init methods of related beans.
@Configuration
public class BootstrappingConfiguration {
@Bean
public FlywayDataSourceContext flywayDataSourceContext(TaskExecutor bootstrapExecutor) {
DefaultFlywayDataSourceContext dataSourceContext = new DefaultFlywayDataSourceContext();
dataSourceContext.setBootstrapExecutor(bootstrapExecutor);
return dataSourceContext;
}
@Bean
public TaskExecutor bootstrapExecutor() {
return new SimpleAsyncTaskExecutor("bootstrapExecutor-");
}
}
@RunWith(SpringRunner.class)
@AutoConfigureEmbeddedDatabase
@ContextConfiguration(classes = BootstrappingConfiguration.class)
public class FlywayMigrationIntegrationTest {
// class body...
}
Troubleshooting
Connecting to embedded database
When you use a breakpoint to pause the tests, you can connect to a temporary embedded database. Connection details can be found in the log as shown in the example below:
i.z.t.d.l.EmbeddedDatabaseReporter - JDBC URL to connect to the embedded database: jdbc:postgresql://localhost:55112/fynwkrpzfcyj?user=postgres, scope: TestClass#testMethod
If you are using @FlywayTest
annotation, there may be several similar records in the log but always with a different scope. That's because in such case multiple isolated databases may be created.
Process [/tmp/embedded-pg/PG-XYZ/bin/initdb, ...] failed
Try to remove /tmp/embedded-pg/PG-XYZ
directory containing temporary binaries of the embedded postgres database. That should solve the problem.
Running tests on Windows does not work
You probably need to install the Microsoft Visual C++ 2013 Redistributable Package. The version 2013 is important, installation of other versions does not help. More detailed is the problem discussed here.
Running tests inside Docker does not work
Running build inside Docker is fully supported, including Alpine Linux. But you must keep in mind that the PostgreSQL database must be run under a non-root user. Otherwise, the database does not start and fails with an error.
So be sure to use a docker image that uses a non-root user, or you can use any of the following Dockerfiles to prepare your own image.
Standard Dockerfile
FROM openjdk:8-jdk
RUN groupadd --system --gid 1000 test
RUN useradd --system --gid test --uid 1000 --shell /bin/bash --create-home test
USER test
WORKDIR /home/test
Alpine Dockerfile
FROM openjdk:8-jdk-alpine
RUN addgroup -S -g 1000 test
RUN adduser -D -S -G test -u 1000 -s /bin/ash test
USER test
WORKDIR /home/test
Using Docker provider inside a Docker container
This combination is supported, however, additional configuration is required.
You need to add -v /var/run/docker.sock:/var/run/docker.sock
mapping to the Docker command to map the Docker socket.
Detailed instructions are here.
Frequent and repeated initialization of the database
Make sure that you do not use org.flywaydb.test.junit.FlywayTestExecutionListener
. Because this library has its own test execution listener that can optimize database initialization.
But this optimization has no effect if FlywayTestExecutionListener
is also applied.
ERROR: role "..." already exists
Since version 1.4.0, database prefetching has been improved. All databases are stored within a single database cluster. It speeds up the preparation of databases, but in some rare cases, if your database scripts use some global objects inappropriately, this change can cause problems. If necessary, you can change this behavior back by setting the following property:
zonky.test.database.postgres.zonky-provider.preparer-isolation=cluster
Building from Source
The project uses a Gradle-based build system. In the instructions
below, ./gradlew
is invoked from the root of the source tree and serves as
a cross-platform, self-contained bootstrap mechanism for the build.
Prerequisites
Git and JDK 8 or later
Be sure that your JAVA_HOME
environment variable points to the jdk1.8.0
folder
extracted from the JDK download.
Check out sources
git clone git@github.com:zonkyio/embedded-database-spring-test.git
Compile and test
./gradlew build
Project dependencies
- PostgreSQL Binaries (10.7)
- Embedded Postgres (1.2.3) - a fork of OpenTable Embedded PostgreSQL Component
- Spring Framework (4.3.22) -
spring-test
,spring-context
modules - Flyway (5.0.7)
- Guava (23.0)
License
The project is released under version 2.0 of the Apache License.