When one is writing software, testing is a big part of it. Unit tests of algorithms are pretty easy do achieve and run and are probably (hopefully!) an accepted standard these days.

Units waiting to be tested

Things are getting more interesting when external dependencies come into play. This is often called integration testing. But in reality what one wants to test is not really the integration into the target environment, but rather that basic assumptions on how to interact with other systems are met. Very often mock objects come into play, that for example abstract away database access to provide result objects. Unfortunately this leaves the questions a) who tests the mocks and b) how well does a mock object represent reality? But I digress.

Another way of doing those tests is to deploy the dependencies and then run your code against those dependencies. When doing this in the classical way this means a lot of pain. Probably a central DB server or Message bus or the like where you can connect to. Each developer gets their own schema or destination (-prefix) or the like so the likelihood to step on each others toes is minimal. Or developers set up their own local instances of the dependencies which may clash with the setup for a different project and/or constantly use scarce system resources.

Testcontainers to the rescue

Luckily Linux containers have been an established part of the IT environment for a few years now. Containers are quick to start and can confine the software that runs within. Stopping (and deleting) a container also cleans up changes so that for a next run defined start / base can be established (given that they don’t persist changes in the host file system).

Testcontainers is now a way to easily start container images from Java code with such dependencies and then make them available to your tests. Testcontainers are well integrated with test frameworks like JUnit.

Using PostgreSQL

Let’s have a look at some examples and start with a Postgres Database:

@QuarkusTest // (1)

class RestApiTest {



@ClassRule

private static PostgreSQLContainer postgreSQLContainer =

new PostgreSQLContainer("postgres"); // (2)

Here we say that this is a test class that should run tests with firing up Quarkus, as this is my application server of choice (1) and then set up a Postgres container. The argument ‘postgres’ is actually the image name and could be omitted. Now that we have this, we need to start a container from the image and tell our code where it could find it:

@BeforeAll

private static void setupPostgres() throws Exception {

postgreSQLContainer.start(); // (3)

// Now that postgres is started, we need to get its URL and tell

// Quarkus

System.setProperty("quarkus.datasource.url",

postgreSQLContainer.getJdbcUrl());

System.setProperty("quarkus.datasource.username","test");// (4)

System.setProperty("quarkus.datasource.password","test");

(3) starts the container and the Postgres instance inside. Once it is running, we can obtain the JDBC-URL from it and pass it to Quarkus via system properties (4). We also need to set the credentials to access the database. This is hardcoded ‘test’ in the example because this is what I found in the source of PostgreSQLContainer, but ideally one should parametrise it as one can see below.

Next we need to set up a schema (unless your code does that like with Hibernates auto-DDL mode). I am using Liquibase here, but it is also possible to pass the DDL script via parameter.

PGSimpleDataSource ds = new PGSimpleDataSource(); // (5)



// Datasource initialization

ds.setUrl(postgreSQLContainer.getJdbcUrl());

ds.setUser(postgreSQLContainer.getUsername());

ds.setPassword(postgreSQLContainer.getPassword());



DatabaseConnection dbconn = new JdbcConnection(ds.getConnection());

ResourceAccessor ra = new FileSystemResourceAccessor("src/test/sql");

Liquibase liquibase = new Liquibase("dbinit.sql", ra, dbconn);//(6)

liquibase.dropAll();

liquibase.update(new Contexts()); // (7)

In (5) I set up a simple datasource, that is then parametrized and a database connection is obtained from it. We then (6) tell Liquibase to use this connection and our DDL script to update the database with the DDL (7), basically setting up the schema and test data.

Using a mock HTTP server

My code is also making http requests to another service, which is not necessarily available during tests of my service. But no problem, there is a Mockserver for this purpose. Again, we first define the server:

@ClassRule

public static MockServerContainer mockServer = new MockServerContainer();

and then afterwards start it:

mockServer.start();

System.err.println("Mock engine at http://" +

mockServer.getContainerIpAddress() + ":" + // (8)

mockServer.getServerPort());

Containers expose their ports at “random” locations to avoid clashes (unless told differently). We need to get the port in (8) and tell our code where the server lives.

Now the question is how does that server know what to serve? There are several ways to do it. One that I like is a fluent API of request matching and providing a response. For this we need org.mock-server:mockserver-client-java on the classpath. The tricky thing is that the client version needs to match the server version from Testcontainer’s Mockserver. After this is solved, we can use it in our setup code:

new MockServerClient(mockServer.getContainerIpAddress(),

mockServer.getServerPort())

.when(request()

.withPath("/api/v1/verifyPolicy")

)

.respond(response()

.withStatusCode(201)

.withHeader("Content-Type","application/json")

.withBody("{ \"msg\" : \"ok\" }")

);

System.setProperty("engine/mp-rest/url",

"http://" +

mockServer.getContainerIpAddress() +

":" +

mockServer.getServerPort());

Using Kafka

Last but not least, we have a look at setting up Kafka in a Testcontainer and telling the MicroProfile Reactive Messaging client about it:

@ClassRule

private static KafkaContainer kafka = new KafkaContainer();

I think you can see a pattern here :-)

// Start Kafka

kafka.start();

// It is running, so pass the bootstrap server location to Quarkus

String kafkaBootstrap = kafka.getBootstrapServers();

System.setProperty("kafka.bootstrap.servers", kafkaBootstrap);

This basic setup is so straightforward, that there is no point in dealing with all the legacy hassle.

Conclusion

Testcontainers can give you a way to very easily set up a relatively complex environment to test your code. This setup can be used with CI-systems like Travis or other places that support Docker containers (Testcontainers relies on a dockerd being present, so currently does not support other container runtimes) which include developer machines.

The code for (most of) the examples above can be found online.