Writing integration tests (Java)

REVIEW

Overview

This guide explains how to implement and configure Integration Tests using the Maven Failsafe plugin, in a Gitlab-CI build environment. As an example, we use a Postgres Database running in Docker with a schema defined using Liquibase. Other external dependant services could be wired up in a similar fashion.

Maven testing plugins and their lifecycles

Maven provides two plugins to run tests, each one configured to run in different Maven lifecycle phases:

  • Surefire plugin

    • test phase
      Run unit tests.
  • Failsafe plugin

    • pre-integration-test phase
      Downstream resources/services are setup. E.g. liquibase can be run to instantiate a schema in a database.
    • integration-test phase
      Run integration tests.
    • post-integration-test phase
      Downstream resources/services are torn down. E.g. liquibase rollback can be executed to test that rollback has been correctly defined.
    • verify phase
      Fail the build according to integration test results.

The important benefit of the Failsafe plugin is the facility to setup and teardown resources outside of the tests themselves. The post-integration-test phase always runs regardless of integration test failures.

The maven surefire plugin is configured to run by default. The failsafe plugin is not. You must add the plugin to your pom.xml.

Consider if the integration tests should always run. If the downstream resource isn’t always going to be available, (e.g. in your dev environment), place the configuration within a profile.

<profiles>
    <profile>
        <id>integration</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-failsafe-plugin</artifactId>
                    <executions>
                        <execution>
                            <goals>
                                <goal>integration-test</goal>
                                <goal>verify</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Integration tests can be switched on with the integration profile:

mvn -Pintegration clean install

Write an integration test

With the failsafe plugin configured you can write an integration test. This example tests that an Entity is saved.

Given the entity:

@Entity
@Data
public class Person {

    @Id
    @GeneratedValue(strategy=IDENTITY)
    private Long id;

    @Version
    private int version;

    private String firstName;
    private String middleNames;
    private String surname;
    ...

And this test:

@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace=Replace.NONE)
public class PersonIT {

    @Autowired
    protected TestEntityManager entityManager;

    @Autowired
    protected JdbcTemplate jdbcTemplate;

    @Test
    public void saveEntity() {

        Person fixture = new Person("first", null, "last");
        entityManager.persistAndFlush(fixture);
        
        assertThat(JdbcTestUtils.countRowsInTable(jdbcTemplate, "person"), equalTo(1));

    }
}

This should run during the integration-test phase in maven:

mvn -Pintegration clean install

This would will fail without an active database. As its easy to run a production-like PostgreSQL instance in Docker, we may as well test against the real thing.

Gitlab-CI and Docker

Gitlab-CI runs by default within a Docker container. This allows us to add downstream services as other Docker containers with network access between our tests and the services. In our case, we’re going to run Postgres as a service within Gitlab-CI.

The following code block is a snippet of configuration from a gitlab-ci.yml file:

Verify Feature:
  stage: Build
  services:
    - name: dps-nexus.service.nhsbsa:8444/nhsbsa/postgres:9.6

Here we have configured a dependant service of Postgres that is retrieved from the NHSBSA’s own Docker repository (Nexus). This build will set us up with standard user accounts that we’ll need to access the database and apply schema changes using liquibase (see below).

But, you may choose to use a different Docker image with a pre-populated schema to suit your needs. Here are some examples:

  • CRS: dps-nexus.service.nhsbsa:8444/crs/crs-db
  • PPC: dps-nexus.service.nhsbsa:8444/ppc/ppc-db
  • EIBSS: dps-nexus.service.nhsbsa:8444/eibss/eibss-db

Linking with DNS

When Gitlab-CI runs its docker containers, it will expose a simple DNS lookup to the service hostname, using a naming convention based on the Docker image name. We can use this hostname within our test configuration.

For instance, in this snippet, we configured access to the postgres service declared above:

spring:
  datasource:
    url: jdbc:postgresql://dps-nexus.service.nhsbsa__nhsbsa__postgres:5432/lis_pgdb

Configuring with profiles

So we have a running database, and our tests are configured to use the hostname Gitlab-CI will provide, but we only want to use this configuration when running in Gitlab.

The answer is profiles: Maven profiles and Spring profiles. The steps are:

  • Configure the test based on active Spring profile
    Spring Boot tests will pick up configuration based on a naming convention (item 13), which makes it simple to add the configuration listing above to your test/resources folder using the name: application-gitlabci.yml
  • Activate a Spring profile from a Maven profile
    Now that a Spring profile will activate the Gitlab-CI specific configuration, we need to activate that profile when Maven runs its tests on Gitlab-CI.

First, add this to your pom.xml:

<profiles>
    <profile>
        <id>gitlabci</id>
        <build>
        <pluginManagement>
            <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                <systemPropertyVariables>
                    <spring.profiles.active>gitlabci</spring.profiles.active>
                </systemPropertyVariables>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <configuration>
                <systemPropertyVariables>
                    <spring.profiles.active>gitlabci</spring.profiles.active>
                </systemPropertyVariables>
                </configuration>
            </plugin>
            </plugins>
        </pluginManagement>
        </build>
    </profile>
</profiles>

And then add this to your gitlab-ci.yml:

Verify Feature:
...
  services:
    - name: dps-nexus.service.nhsbsa:8444/nhsbsa/postgres:9.6
  script:
    - mvn -e -B -Pgitlabci clean install

Note: the mvn command above is incomplete and would need code quality directions adding.

Liquibase

Why test with Liquibase and a production-like database?

  • Testing against an in-memory database will not catch the edge cases when something works in H2, but not in Postgres. It’s much safer to test against the same DB engine as production.
  • You can test the liquibase scripts at the same time

Try to follow best practice when using Liquibase but in addition:

  • Align the individual versioned changelog files with the pom version
  • Align the change-sets to the pom version with an incremental number
  • Use your NHSBSA cipher/LAN ID as the author

E.g. for db.changelog-1.0.xml

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd">
  <changeSet author="PATTU" id="1.0-1">
    <createTable tableName="my_table">
      <column name="my_column" type="TEXT">
      </column>
    </createTable>
  </changeSet>
</databaseChangeLog>

Maven and Liquibase

Liquibase provides a Maven plugin to execute Liquibase commands within the build lifecycle. A sample pluginManagement snippet is shown below:

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.liquibase</groupId>
                <artifactId>liquibase-maven-plugin</artifactId>
                <version>3.5.3</version>
                <configuration>
                    <promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
                    <driver>org.postgresql.Driver</driver>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.yaml</groupId>
                        <artifactId>snakeyaml</artifactId>
                        <version>${snakeyaml.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.postgresql</groupId>
                        <artifactId>postgresql</artifactId>
                        <version>${postgres.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

Note: plugin dependencies above show support YAML based configuration and the Postgres database driver. Your requirements may differ.

To call the Liquibase plugin within the pre-integration-test phase, add the following snippet:

<profiles>
    <profile>
        <id>liquibase</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.liquibase</groupId>
                    <artifactId>liquibase-maven-plugin</artifactId>
                    <configuration>
                        <changeLogFile>src/main/resources/db/changelog/db.changelog-master.yml</changeLogFile>
                        <propertyFile>src/test/resources/liquibase.properties</propertyFile>
                    </configuration>
                    <executions>
                        <execution>
                            <id>liquibase-update</id>
                            <phase>pre-integration-test</phase>
                            <goals>
                                <goal>update</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>
  • <changeLogFile> specifies the location of the main changelog
  • <propertyFile> specifies the location of liquibase.properties.
    You can use this to specify properties for local development

The gitlab-ci.yml file will override these properties for the pipeline

References


Improve the playbook

If you spot anything factually incorrect with this page or have ideas for improvement, please share your suggestions.

Before you start, you will need a GitHub account. Github is an open forum where we collect feedback.