Isolate database in tests

115 views
Skip to first unread message

Arnaud Pflieger

unread,
Aug 4, 2017, 6:27:09 AM8/4/17
to Play Framework
Hi,

I am struggling with database config. I am probably doing something wrong.

I am on Play 2.6.1, I use EBean. I disabled Evolutions but I still use generated sql scripts to create the database schema in each test using Evolutions.applyEvolutions.
At runtime, the datasource is postgres and I use H2 in tests.
Currently I'm doing this by overriding the 'default' datasource config in tests with this code
Map<String, Object> additionalConfig = new HashMap<>();
additionalConfig.putAll(inMemoryDatabase());
return new GuiceApplicationBuilder()
.configure(additionalConfig)
.build();

And then in tests I do

Database db = app.injector().instanceOf(Database.class);
Evolutions.applyEvolutions(db);

This works but I have a problem: when I display a web page of my app, Evolutions writes the schema for postgres in 1.sql. Then I run tests, they all fail because 1.sql is written in postgres, not H2 but Evolutions rewrites 1.sql for H2 during this execution. So I have to run tests a 2nd time to make it work.

There's several questions here. 
Is there a better way to create the database schema than using 1.sql?
Do I need to isolate datasources bewteen test and runtime?

Arnaud Pflieger

unread,
Aug 8, 2017, 12:25:38 PM8/8/17
to Play Framework
Did anyone achieved or tried to do that? 

Steve Chaloner

unread,
Aug 11, 2017, 3:53:14 AM8/11/17
to Play Framework
Instead of using H2 for testing, use TestContainers instead (https://www.testcontainers.org/usage/database_containers.html) - this will create a Docker container with a Postgres database (a few other flavours are available too).  You can then populate the db with your schema, load up test data and run your tests against it.  I wrote a couple of blog posts about this - see http://www.objectify.be/wordpress/2017/03/28/database-testing-with-testcontainers/ and http://www.objectify.be/wordpress/2017/05/17/boosting-test-performance-with-testcontainers/

Arnaud Pflieger

unread,
Aug 22, 2017, 5:34:50 PM8/22/17
to Play Framework
Thank you Steve

I have a lot of tests using the database so i'm a bit afraid of this approach.

I figured out how to achieve what I wanted. I struggled quite a lot so i'm dropping here the config, if that helps.

Reminder : I use postgres for dev/prod with evolution. H2 in tests. Since the sql dialect is different, I need to isolate datasources so scripts
I have separate config files for each env. They all include a common application.conf. The trick is to not declare datasource at all in application.conf

Then in test.conf
play.db.default = "test" // The test datasource config is provided dynamically in tests
ebean.test = ["com.meetinclass.models.*"]

prod.conf and dev.conf are the same (exepct the url)
db.default.driver = org.postgresql.Driver
db.default.url = "postgres://..."
ebean.default = ["com.meetinclass.models.*"]

In @Test I start play app with 
play.test.Helpers.inMemoryDatabase("test") // configures dynamically the test datasource 

Dušan Škerget

unread,
Nov 7, 2017, 6:59:04 AM11/7/17
to Play Framework
Hi, not really sure if you solved it or not, but we have similar setup and this is how I did it (it might also help someone else):

public class IntegrationTest {

    protected static Application app;

    //overwriting application.conf file
    private static final Map<String, Object> configMap = ImmutableMap.<String, Object>builder()
            .put("db.default.driver", "org.h2.Driver")
            .put("db.default.url", "jdbc:h2:mem:test")
            .put("db.default.username", "sa")
            .put("db.default.password", "")
            .put("play.evolutions.enabled", "false")
            .build();

    @BeforeClass
    public static void provideApplication() {

        Config config = ConfigFactory.parseMap(configMap);

        app = new GuiceApplicationBuilder().configure(config).build();
        Helpers.start(app);
    }

    @AfterClass
    public static void stopApp() {
        Helpers.stop(app);
        app = null;
    }

    @Test
    public void testIndex() {
        Http.RequestBuilder request = new Http.RequestBuilder()
                .method(GET)
                .uri("/");

        Result result = route(app, request);
        assertEquals(OK, result.status());
        assertEquals("There must be 3 records", play.test.Helpers.contentAsString(result), "3");
    }

}

Above code will overwrite default application.conf configuration with new one. Then as we also want to load seed.sql data, I have test-ebean.properties file under resources with content:
ebean.ddl.generate=true
ebean.ddl.run=true
ebean.ddl.seedsql=seed.sql

This will now generate and run migrationd and load all SQLs from seed.sql into inmemory db.

Regards

Arnaud Pflieger

unread,
Nov 7, 2017, 8:52:03 AM11/7/17
to Play Framework
Hi,

Yes, I ended up doing the same as you. Even simpler:
import static play.test.Helpers.inMemoryDatabase;

public class ApplicationTest {

@Inject
private Application app;

@Before
public void before() {
GuiceApplicationBuilder builder = new GuiceApplicationBuilder()
.configure(new HashMap<>(inMemoryDatabase()));
Guice.createInjector(builder.applicationModule()).injectMembers(this);
Helpers.start(app);

}

@After
public void after() {
Helpers.stop(app);
}
}

The 'injectMembers(this)' allows to get injection on subclasses members, which is pretty cool.

And the EBean config which replaces play evolution
ebean.ddl.generate=true
ebean.ddl.run=true
I disabled play evolution statically in the config
Reply all
Reply to author
Forward
0 new messages