Reset Your H2 Database For A Clean State Between Functional Tests

For automated tests, a solution to reset your H2 database between each test..

David Norton

If you’re running automated functional testing as part of your build process, you probably know about test isolation (Martin Fowler has a great article on this and other issues with test non-determinism):

  1. A single test can be run by itself, without the rest of the test suite.
  2. A single test failure should not prevent any other tests from running successfully.
  3. Tests can be run in any order.

For test isolation, you need a clean application state between tests. This can be accomplished in several ways, and depends on your problem domain. For example, if your application is multi-tenant, you could have each test create a new tenant. A tenant could be something as simple as a user.

We approached our automated functional testing in a slightly different way: resetting the entire database between tests. But first, a little background:

  1. Developers are writing automated browser tests using Geb.
  2. These Geb tests are run as part of build process against a local server - they are not run against a shared environment. There is a separate QA-managed test suite that is responsible for testing in an integration environment.
  3. The local server is running against an in-memory H2 database and stubbed third-party services, and starts up with a clean set of test data.

As we began writing our Geb tests, we had a cleanup section in each test to reverse changes we had made: delete records, un-update records, etc. However, this quickly ballooned out of control as it was impossible to know in advance where a test would fail, so we had to build a complicated set of logic in the cleanup block of each test (delete record, but only if it exists). The tests were difficult to maintain and that was a problem.

As we were running against an H2 database, it was quite simple to reset the database between tests. (We did not use Spring’s @Transactional on integration tests because we wanted our tests to run against an application server, not just a Spring application context.)

TL;DR - how do I reset the database between tests?

Step 1: Configure H2 to save to a local file

spring.datasource.url=jdbc:h2:file:/tmp/myApplicationDb;AUTO_SERVER=TRUE
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver

This saves the database to a location in the file system, and utilizes an H2 feature that allows multiple processes to connect to the same database.

Step 2: Backup and reload database before and after tests

We created a Groovy trait with Spock setup and cleanup methods. If your tests are not all in Spock, or you can’t use Groovy 2.3 traits, you could try some other method for code-reuse, such as static methods or test class inheritance.

import groovy.sql.Sql

trait ResetsDatabase {
    private static final String H2_BACKUP_LOCATION = '/tmp/h2backup.sql'

    void setup() {
        if (new File(H2_BACKUP_LOCATION).exists()) {
            print "Previous test was interrupted and did not clean up after itself. "
            loadH2FromBackup()
        }
        else {
            Sql sql = connectToSql()
            sql.execute("SCRIPT TO ?", [H2_BACKUP_LOCATION])
        }
    }

    void cleanup() {
        loadH2FromBackup()
        new File(H2_BACKUP_LOCATION).delete()
    }

    private void loadH2FromBackup() {
        println "Restoring H2 from backup location."
        Sql sql = connectToSql()
        sql.execute("DROP ALL OBJECTS")
        sql.execute("RUNSCRIPT FROM ?", [H2_BACKUP_LOCATION])
    }

    private Sql connectToSql() {
        Sql.newInstance('jdbc:h2:file:/tmp/myApplicationDb;AUTO_SERVER=TRUE', 'sa', '', 'org.h2.Driver')
    }
}
class MyApplicationSpec extends GebSpec implements ResetsDatabase {
    def "I can create records" () {
        when:
        go "/records"
        $('.add-record').click()

        then:
        $('li.record').size() == 1
    }

    def "clean state"() {
        when:
        go "/records"

        then:
        $('li.record').size() == 0
    }
}

And that’s it! Your functional tests will all start with an environment in the same state, regardless of how the previous test ended up. I’ve created a Github repo using Spring Boot, if you’re looking for a fleshed-out example.

Share this Post

Related Blog Posts

JVM

Grails cdn-asset-pipeline Plugin for Improving Web Site Performance

January 6th, 2015

Easily push your Grails assets to a S3 backed CDN with the cdn-asset-pipeline.

Patrick Double
JVM

Copying multiple directories in a single Gradle task with up-to-date checking

December 11th, 2014

How to write a single Gradle task to copy multiple directories and still use up-to-date checking.

Object Partners
JVM

Case-insensitive criteria ordering on child properties

December 3rd, 2014

A workaround for a couple of Grails 2.x bugs and changes that have made it difficult to sort criteria case-insensitively when using child/nested properties.

Igor Shults

About the author

David Norton

Principal Consultant

Software engineer with 9 years of professional application development experience. Passionate about continuous delivery, incremental improvement, and test-driven development.

Background heavy in enterprise Java technologies such as Groovy, Spring, Spock, Gradle, Hibernate, Tomcat, Jenkins. Focus on high-scale web architecture, platform transformation, and team development.