Developer Blog

Using Play Framework's in Memory Database to Improve Test Speed

I’ve been working with Play Framework 2.x in scala for the last few months and overall I’m pretty happy. One big issue I ran into recently was that if you want to use a real database, it becomes hard to convince Play Framework to run your tests against H2 (for the order of magnitude speed increase).

The Secret Sauce

The trick that finally made it work was found by digging through the Play source. You can tell your app to use a specific data source while running in test mode. The easiest way to set this up is to give yourself a trait that you will extend classes and objects with:

1
2
3
4
5
6
7
8
9
trait DatabaseClient {
  private def dbName = if (Mode.Test == current.mode) "test" else "default"

  def db = Database.forDataSource(DB.getDataSource(dbName))

  def withSession[A](f: => A): A = db.withSession(f)

  def withTransaction[A](f: => A): A = db.withTransaction(f)
}

This allows you to run a different DB config while in test mode.

Getting The Test Environment To Work

While the Play documentation tells you that you can just add multiple datasources to your application.conf file, what they fail to mention is that your app will try to use all specified DBs in all environments. Instead of placing the config for your test database in your config file, I inject it directly into the FakeApplication that I instantiate for my tests:

1
2
3
4
5
6
7
trait TestHelpers { self: Specification =>

  def fakeApplication = FakeApplication(additionalConfiguration = inMemoryDatabase("test") + (
    "applyEvolutions.test" -> "true"
  ))

}

It’s useful to have this in its own trait because you will likely want to use fakeApplication in a few instances (we have to reference it in our model tests as well as our controller tests). Now that you have a configuration for your “test” database, and due to the change above, when your tests run, they will use H2.

Note that if you are using Typesafe Slick, you’ll want to inject a "slick.test" -> "models.*" in there.

Providing your FakeApplication for your tests

In order to get my tests to run in the context of an H2 database, I use a trait that can wrap my specs for me:

1
2
3
4
5
6
7
8
9
trait TestSupport { self: Specification =>

  trait WithApplication extends Around {
    def around[T : AsResult](t: => T) = running(fakeApplication) {
      AsResult(t)
    }
  }

}

then in my spec:

1
2
3
4
5
6
7
8
9
10
11
class ModelSpec extends Specification with TestSupport {

  "Model" should {

    "be creatable" in new WithApplication {
      Model.create() must beSome[Model]
    }

  }

}

The nice thing about this setup is that it’s flexible to meet the demands of most apps. Adding changes to the config is always a black box, it’s hard to reason about how the framework will react to those changes. Moving this to actual code makes it more obvious what is actually happening, and lets you go even further, if, for example, you wanted to have your model tests run against MySql, but controller tests use H2 (not sure why you would want this…).

Caveat

As far as I can tell, Play will try to maintain a connection to your database regardless of whether or not you deliberately try to connect to it (if it’s in the config, it tries to connect). This means the tests ran fine on my machine, but were failing on our CI (circleci.com). The fix is to make sure that your connection to the database is still valid, so all I had to do was update the user/pass/dbname in my CI config and I was all set. It feels a bit sloppy that Play connects to a database you aren’t even using, so if anyone has any suggestions on how to prevent this behavior, let me know and I’ll update this post.

Comments