April 11, 2019
When you are testing code which uses a database in Spring tests there are a few options:
The functionality I wanted to test used postgres specific features (JSON columns) which weren’t available in H2. For continuous integration I was using concourse. It’s currently not possible to run docker containers in concourse, so if you want to use the popular and helpful TestContainers library to provide ephemeral test services such as a databases to your tests you are out of luck (unless you want to run docker in docker…).
So when I was faced with this problem I went down the route of using an embedded postgres in the Spring unit tests.
Unfortunately the most popular library for running an embedded postgres (whilst recently becoming unmaintained) has some problems shutting down which caused failures when multiple tests were run together. A previous postgres instance would still be running and the new postgres would fail to start because it left some resources (files) which conflicted and as the spring app was configured to point to the new postgres url it couldn’t connect.
The solution to this problem was to run postgres only once for the whole test execution rather than once per test class. This has the added benefit of a faster test run which is important when the test run is already taking more time than before downloading the database.
JUnit5 provides this functionality via extensions and a global store of classes which are closed on exit. Below is an example of how to use this combination of global store and extensions to start postgres once for the whole test run.
public class EmbeddedPostgresExtension implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {
private static boolean started = false;
private EmbeddedPostgres postgres;
@Override
public void beforeAll(ExtensionContext context) {
if (!started) {
started = true;
context.getRoot().getStore(GLOBAL).put(EmbeddedPostgresExtension.class.getName, this);
postgres = new EmbeddedPostgres(V9_6);
postgres.start("localhost", 45632, "databaseName", "user", "password");
}
}
@Override
public void close() {
postgres.stop();
}
}
@ExtendWith(SpringExtension.class)
@ExtendWith(EmbeddedPostgresExtension.class)
@ActiveProfiles("postgres")
@SpringBootTest
public class SpringTest {
@Test
public void test {
//Postgres will be running when this test executes
//And will shut down after the whole test execution completes (after all tests, in all classes)
}
}
Provide the configuration to connect to the embedded postgres in src/test/resources/application-postgres.yml
:
spring.datasource:
url: jdbc:postgresql://localhost:45632/databaseName
username: user
password: password
I found there was no need to use random free ports to start postgres on. Choosing a random port above 30000 is sufficient because it always shuts down after the test run, meaning the port will always be free. However if you really wanted to decouple this extension from your tests you could set the URL as a parameter to be injected into the test.
You can set the spring properties for the embedded postgres in a number of ways. Putting it in an application-postgres.yml
and annotating the test class with an @ActiveProfiles("postgres")
will ensure the test has the right config before starting the spring context. You can also set the variables as system properties before the spring context starts to allow for flexibility i.e. if you use a super class for common test config and tests in other subclasses don’t use postgres.
Written by Phil Hardwick