org.kiwiproject.test.dropwizard.app.PostgresAppTestExtension Maven / Gradle / Ivy
package org.kiwiproject.test.dropwizard.app;
import io.dropwizard.Application;
import io.dropwizard.Configuration;
import io.dropwizard.testing.ConfigOverride;
import io.dropwizard.testing.ResourceHelpers;
import io.dropwizard.testing.junit5.DropwizardAppExtension;
import io.zonky.test.db.postgres.embedded.LiquibasePreparer;
import io.zonky.test.db.postgres.junit5.EmbeddedPostgresExtension;
import io.zonky.test.db.postgres.junit5.PreparedDbExtension;
import lombok.Getter;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.postgresql.Driver;
/**
* Multipurpose extension for Dropwizard {@link Application} testing requiring a PostgreSQL database. The extension
* will spin up an embedded PostgreSQL database instance, run the migrations and then configure the
* {@link DropwizardAppExtension} with the database information.
*
* There are several things to note when using this extension:
*
* -
* The embedded PostgreSQL extension supports both Flyway and Liquibase, but we are only supporting Liquibase
* currently.
*
* -
* The application's {@link Configuration} class must have a Dropwizard
* {@link io.dropwizard.db.DataSourceFactory DataSourceFactory}. By default the property name is expected to
* be "database", i.e. the property will either have {@code @JsonProperty("database")} or be named "database",
* but you can change this using the alternate constructor.
*
* -
* The YAML configuration file provided to this extension can contain a
* {@link io.dropwizard.db.DataSourceFactory DataSourceFactory} property with custom values, but this extension
* always overrides the following properties: {@code driverClass}, {@code user}, and {@code url} because the
* driver is always Postgres, and the user and url are provided by the embedded Postgres database.
*
* -
* You should not register {@code DropwizardExtensionsSupport} or the embedded Postgres
* extension. See the WARNING below.
*
*
*
* To include this extension in an Application test, add the following at the top of the class:
*
* {@literal @}RegisterExtension
* public static PostgresAppTestExtension<AppConfiguration> POSTGRES_APP =
* new PostgresAppTestExtension("migrations.xml", "config.yml", App.class);
*
* Here is a one of the simplest {@code config.yml} files you can have:
*
* ---
* server:
* type: simple
*
* Note that you do not need to declare a database property because this extension overrides the
* required properties. But, you can add other custom properties to the configuration if desired:
*
* ---
* server:
* type: simple
*
* database:
* initialSize: 1
* minSize: 1
* maxSize: 5
*
*
* Most of the time these won't really matter in unit/integration testing scenarios. Next is a fragment of a sample
* Configuration class that uses the default property name:
*
* {@literal @}JsonProperty("database")
* private DataSourceFactory dataSourceFactory = new DataSourceFactory();
*
* The extension assumes the configuration uses {@code "database"} as the name of the DataSourceFactory property, as
* shown in the above examples which specify the name via the {@code JsonProperty} annotation. If the property in
* your configuration is named something else, you can use the alternate constructor which accepts a custom property
* name. For example:
*
* // Uses "db" as the name of the DataSourceFactory in the configuration
* {@literal @}RegisterExtension
* public static PostgresAppTestExtension<AppConfiguration> POSTGRES_APP =
* new PostgresAppTestExtension("migrations.xml", "config.yml", App.class, "db");
*
*
* The test extension instance can access the application and the PostgreSQL instance by calling
* {@code POSTGRES_APP.getApp()} and {@code POSTGRES_APP.getPostgres()} respectively.
*
* You can also provide one or more {@link ConfigOverride} values to the extension:
*
* {@literal @}RegisterExtension
* public static PostgresAppTestExtension<AppConfiguration> POSTGRES_APP =
* new PostgresAppTestExtension("migrations.xml", "config.yml", App.class,
* ConfigOverride.config("someValue", "42"),
* ConfigOverride.config("aLazyValue", () -> calculateTheLazyValue()));
*
*
* WARNING: When using this extension you should not register
* {@code DropwizardExtensionsSupport} or the embedded Postgres extension since this extension programmatically
* registers both of them. Doing so will almost certainly result in unexpected behavior such as
* {@code NullPointerException}s being thrown.
*
* For information on how the embedded PostgreSQL extension works see:
* https://github.com/zonkyio/embedded-postgres
*
* For information on how the dropwizard app extension works see:
* https://www.dropwizard.io/en/latest/manual/testing.html#junit-5
*
* @param the {@link Configuration} implementation for the application
*/
@Getter
public class PostgresAppTestExtension implements BeforeAllCallback, AfterAllCallback {
private static final String DEFAULT_DATASOURCE_FACTORY_PROPERTY = "database";
private final PreparedDbExtension postgres;
private final DropwizardAppExtension app;
/**
* Create a new instance using the default property name ("database") for the DataSourceFactory.
*
* @param migrationClasspathLocation the classpath location of the Liquibase migrations file to use
* @param configFileName the name of the classpath resource to use as the application configuration file
* @param appClass the Dropwizard application class
* @param configOverrides optional configuration override values
*/
public PostgresAppTestExtension(String migrationClasspathLocation,
String configFileName,
Class extends Application> appClass,
ConfigOverride... configOverrides) {
this(migrationClasspathLocation, configFileName, appClass, DEFAULT_DATASOURCE_FACTORY_PROPERTY, configOverrides);
}
/**
* Create a new instance with a custom property name for the DataSourceFactory.
*
* @param migrationClasspathLocation the classpath location of the Liquibase migrations file to use
* @param configFileName the name of the classpath resource to use as the application
* configuration file
* @param appClass the Dropwizard application class
* @param dataSourceFactoryPropertyName the name of the DataSourceFactory property in the Configuration class,
* i.e. what is specified in the YAML configuration
* @param configOverrides optional configuration override values
*/
public PostgresAppTestExtension(String migrationClasspathLocation,
String configFileName,
Class extends Application> appClass,
String dataSourceFactoryPropertyName,
ConfigOverride... configOverrides) {
var liquibasePreparer = LiquibasePreparer.forClasspathLocation(migrationClasspathLocation);
postgres = EmbeddedPostgresExtension.preparedDatabase(liquibasePreparer);
var dbUserProperty = dataSourceFactoryPropertyName + ".user";
var dbUrlProperty = dataSourceFactoryPropertyName + ".url";
var dbDriverClassProperty = dataSourceFactoryPropertyName + ".driverClass";
var userConfigOverride = ConfigOverride.config(dbUserProperty, () -> postgres.getConnectionInfo().getUser());
var urlConfigOverride = ConfigOverride.config(dbUrlProperty,
() -> "jdbc:postgresql://localhost:" + postgres.getConnectionInfo().getPort()
+ "/" + postgres.getConnectionInfo().getDbName());
var driverConfigOverride = ConfigOverride.config(dbDriverClassProperty, Driver.class.getName());
var postgresConfigOverrides = new ConfigOverride[]{userConfigOverride, urlConfigOverride, driverConfigOverride};
var combinedConfigOverrides = ArrayUtils.addAll(postgresConfigOverrides, configOverrides);
app = new DropwizardAppExtension<>(
appClass,
ResourceHelpers.resourceFilePath(configFileName),
combinedConfigOverrides);
}
@Override
public void beforeAll(ExtensionContext extensionContext) throws Exception {
postgres.beforeAll(extensionContext);
app.before();
}
@Override
public void afterAll(ExtensionContext extensionContext) {
app.after();
postgres.afterAll(extensionContext);
}
}