Quarkus tests with Testcontainers and PostgreSQL

Testcontainers is a Java library that allows integrating Docker containers in JUnit tests with ease. In a Containerized World, there is little sense to complicate the tests configuration with embedded databases and services. Instead, use run your services in Docker and let the Testcontainers manage this for you. So if you are need Redis, MongoDB or PostgreSQL in your tests - Testcontainers may become your good friend.

In this blog post you will learn how to configure Testcontainers to manage PostgreSQL instance in Quarkus integration tests.

Table of Contents

Dependencies

In order to use Testcontainers with PostgreSQL you need to add the following dependencies to the pom.xml:

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.12.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.12.5</version>
    <scope>test</scope>
</dependency>

Note: Testcontainers provides JUnit 5 plugin, but in the scenario presented in this article it is not needed.

Test datasource configuration

The default PostgreSQL configuration in Quarkus application may look like below:

quarkus.datasource.url=jdbc:postgresql://localhost:5432/some-db
quarkus.datasource.driver=org.postgresql.Driver
quarkus.datasource.username=user
quarkus.datasource.password=password

By default, this configuration will be used in any active profile. The built-in profiles in Quarkus are: dev, prod and test. The test profile will be used every time you run the @QuarkusTest.

You can adjust test profile by providing the %test. prefix to the configuration properties in src/main/application.properties file. This allows to configure (and launch) PostgreSQL database container via JDBC URL scheme. To achieve this make sure to:

  • set the driver to org.testcontainers.jdbc.ContainerDatabaseDriver. This special driver makes sure Docker container is created and run once we need a datasource in the application,
  • set the dialect explicitly to org.hibernate.dialect.PostgreSQL9Dialect otherwise you get the exception while starting the application:
org.junit.jupiter.api.extension.TestInstantiationException: TestInstanceFactory [io.quarkus.test.junit.QuarkusTestExtension] failed to instantiate test class [pl.codeleak.samples.OwnerResourceTest]: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
 [error]: Build step io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor#build threw an exception: io.quarkus.deployment.configuration.ConfigurationError: Hibernate extension could not guess the dialect from the driver 'org.testcontainers.jdbc.ContainerDatabaseDriver'. Add an explicit 'quarkus.hibernate-orm.dialect' property.
 at io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor.guessDialect(HibernateOrmProcessor.java:715)
  • set the JDBC URL to jdbc:tc:postgresql:latest:///dbname so that Testcontainers knows which PostgreSQL version to use while running the container.

The complete configuration:

# initializes container for driver initialization
%test.quarkus.datasource.driver=org.testcontainers.jdbc.ContainerDatabaseDriver

# dialect must be set explicitly
%test.quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQL9Dialect

# Testcontainers JDBC URL
%test.quarkus.datasource.url=jdbc:tc:postgresql:latest:///dbname

With the configuration in place, you can run the tests and observe that the test container is created for the test:


$ ./mvnw clean test

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running pl.codeleak.samples.OwnerResourceTest
2020-02-27 21:09:21,096 INFO  [io.qua.fly.FlywayProcessor] (build-8) Adding application migrations in path '/Users/rafal.borowiec/Projects/quarkus/quarkus-postgres-sample/target/classes/db/migration' using protocol 'file'
2020-02-27 21:09:23,084 INFO  [org.fly.cor.int.lic.VersionPrinter] (main) Flyway Community Edition 6.1.4 by Redgate
2020-02-27 21:09:23,176 INFO  [org.tes.doc.DockerClientProviderStrategy] (Agroal_1012722761) Loaded org.testcontainers.dockerclient.UnixSocketClientProviderStrategy from ~/.testcontainers.properties, will try it first
2020-02-27 21:09:23,658 INFO  [org.tes.doc.UnixSocketClientProviderStrategy] (Agroal_1012722761) Accessing docker with local Unix socket
2020-02-27 21:09:23,659 INFO  [org.tes.doc.DockerClientProviderStrategy] (Agroal_1012722761) Found Docker environment with local Unix socket (unix:///var/run/docker.sock)
2020-02-27 21:09:23,780 INFO  [org.tes.DockerClientFactory] (Agroal_1012722761) Docker host IP address is localhost
2020-02-27 21:09:23,815 INFO  [org.tes.DockerClientFactory] (Agroal_1012722761) Connected to docker:
  Server Version: 19.03.2
  API Version: 1.40
  Operating System: Docker Desktop
  Total Memory: 1998 MB
2020-02-27 21:09:23,957 INFO  [org.tes.uti.RegistryAuthLocator] (Agroal_1012722761) Credential helper/store (docker-credential-desktop) does not have credentials for quay.io
2020-02-27 21:09:24,857 INFO  [org.tes.DockerClientFactory] (Agroal_1012722761) Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
2020-02-27 21:09:24,857 INFO  [org.tes.DockerClientFactory] (Agroal_1012722761) Checking the system...
2020-02-27 21:09:24,858 INFO  [org.tes.DockerClientFactory] (Agroal_1012722761) ✔︎ Docker server version should be at least 1.6.0
2020-02-27 21:09:25,040 INFO  [org.tes.DockerClientFactory] (Agroal_1012722761) ✔︎ Docker environment should have more than 2GB free disk space
2020-02-27 21:09:25,078 INFO  [🐳 [postgres:latest]] (Agroal_1012722761) Creating container for image: postgres:latest
2020-02-27 21:09:25,135 INFO  [org.tes.uti.RegistryAuthLocator] (Agroal_1012722761) Credential helper/store (docker-credential-desktop) does not have credentials for index.docker.io
2020-02-27 21:09:25,247 INFO  [🐳 [postgres:latest]] (Agroal_1012722761) Starting container with ID: b436fdabccbe51ba3e6d778d4429505473995d4185a200aef35c4c30b2159369
2020-02-27 21:09:25,779 INFO  [🐳 [postgres:latest]] (Agroal_1012722761) Container postgres:latest is starting: b436fdabccbe51ba3e6d778d4429505473995d4185a200aef35c4c30b2159369
2020-02-27 21:09:26,896 INFO  [🐳 [postgres:latest]] (Agroal_1012722761) Container postgres:latest started in PT3.731519S
2020-02-27 21:09:27,107 INFO  [org.fly.cor.int.dat.DatabaseFactory] (main) Database: jdbc:postgresql://localhost:32830/test (PostgreSQL 12.2)
2020-02-27 21:09:27,146 INFO  [org.fly.cor.int.sch.JdbcTableSchemaHistory] (main) Creating Schema History table "public"."flyway_schema_history" ...
2020-02-27 21:09:27,188 INFO  [org.fly.cor.int.com.DbMigrate] (main) Current version of schema "public": << Empty Schema >>
2020-02-27 21:09:27,193 INFO  [org.fly.cor.int.com.DbMigrate] (main) Migrating schema "public" to version 1.0.0 - Init
2020-02-27 21:09:27,201 INFO  [org.fly.cor.int.sql.DefaultSqlScriptExecutor] (main) DB: table "owners" does not exist, skipping
2020-02-27 21:09:27,226 INFO  [org.fly.cor.int.com.DbMigrate] (main) Successfully applied 1 migration to schema "public" (execution time 00:00.050s)
2020-02-27 21:09:27,726 INFO  [io.quarkus] (main) Quarkus 1.2.1.Final started in 5.761s. Listening on: http://0.0.0.0:8081
2020-02-27 21:09:27,727 INFO  [io.quarkus] (main) Profile test activated.
2020-02-27 21:09:27,727 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, flyway, hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, narayana-jta, resteasy, resteasy-jackson]
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 10.091 s - in pl.codeleak.samples.OwnerResourceTest
2020-02-27 21:09:29,975 INFO  [io.quarkus] (main) Quarkus stopped in 0.689s
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  15.375 s
[INFO] Finished at: 2020-02-27T21:09:30+01:00
[INFO] ------------------------------------------------------------------------

See more on database configuration in the official documentation here: https://www.testcontainers.org/modules/databases/

Initializing test database

You have several options to initialize the databse. You can initialize the database with Flyway, but you can also use Testcontainers to run the init script after database is started but before the connection is returned to the code.

Using Flyway to initialize test database

With Flyway, the migration scripts can be adjusted only for the test profile the same way we adjusted the datasource - by adding the %test. prefix to the configuration properties. This way you can setup different migration files locations and/or you can configure different prefix for the migration files:

%test.quarkus.flyway.locations=test_db/migration
%test.quarkus.flyway.sql-migration-prefix=test_

With the above configuration you may need to create test/src/resources/test_db/migration and put in it for example test_1.0.0_schema.sql file (the name pattern is test_version_description.sql).

Tip: Don’t forget to add Flyway Quarkus extension to the pom.xml.

Using Testcontainers to initialize test database

If you don’t want to use Flyway in tests, you may initialize the database with the script loaded by Testcontainers. The file can be loaded either directly from the classpath or from any location. The only thing to do is to change the JDBC URL:

%test.quarkus.datasource.url=jdbc:tc:postgresql:latest:///dbname?TC_INITSCRIPT=file:src/main/resources/init_pg.sql

or

%test.quarkus.datasource.url=jdbc:tc:postgresql:latest:///dbname?TC_INITSCRIPT=classpath:init_pg.sql

Source code

The source code for this article can be found on Github: https://github.com/kolorobot/quarkus-postgres-sample (testcontainers branch)

See also

Popular posts from this blog

Parameterized tests in JavaScript with Jest

macOS: Insert current date shortcut with `Shortcuts.app`