Search code examples
scalaplayframeworkslick

Testing Scala Play + Slick: why is it picking the wrong database?


I'm working on this github project that uses Play 2.5.10 and Slick 3.1.1 (the issue can be reproduced there by running sbt test but can also be checked directly in travis CI). I use a Postgres database configuration default for development and production. I then use a H2 in memory database called test for testing. The default database is configured in conf/application.conf whereas the test database is configured in conf/application.test.conf.

The problem is that for testing I initialize the database with name test but the Application built with GuiceApplicationBuilder is still picking up the default one.

This line is in my build.sbt to pick up the test configuration:

javaOptions in Test += "-Dconfig.file=conf/application.test.conf"

and this is the content of that file:

include "application.conf"

slick.dbs {
  test {
    driver="slick.driver.H2Driver$"
    db.driver="org.h2.Driver"
    db.url="jdbc:h2:mem:test;MODE=PostgreSQL"
    db.username="sa"
    db.password=""
  }
}

My DaoFunSpec base class looks like this:

package dao

import org.scalatest.{BeforeAndAfterAll, FunSpec}
import org.scalatestplus.play.OneAppPerSuite
import play.api.Application
import play.api.db.evolutions.Evolutions
import play.api.db.DBApi

abstract class DaoFunSpec extends FunSpec with OneAppPerSuite with BeforeAndAfterAll {
  lazy implicit val db = app.injector.instanceOf[DBApi].database("test")

  override def beforeAll() {
    Evolutions.applyEvolutions(db)
  }

  override def afterAll() {
    Evolutions.cleanupEvolutions(db)
  }

  def userDao(implicit app: Application) = {
    Application.instanceCache[UserDao].apply(app)
  }
}

Note the line app.injector.instanceOf[DBApi].database("test") but still Play tries to connect to the default database.


Solution

  • Ok your problem is kinda different (or maybe perhaps a little unexpected). This is the line that causes your headache:

    dbApi.databases().foreach(runEvolutions)
    

    Its in: ApplicationEvolutions.scala:42

    It's probably self-explanatory :)

    Still the problem is more involved. You have two databases actually in your test environment (default and test). Now this leads to several problems - one of which you see above (evolutions are tried on each of them). Another is that if you want to use differently named db you can't just inject something like that:

    class UserDao @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
    

    instead you would need to use (AFAIR):

    class UserDao @Inject()(@NamedDatabase("test") protected val dbConfigProvider: DatabaseConfigProvider)
    

    But in that case you testing becomes more complicated.

    It would be all much simpler if:

    1) You would extract common configuration to common.conf

    2) You would change your application.conf to something like this:

    include "common.conf"
    
    slick.dbs {
      default {
        driver="slick.driver.PostgresDriver$"
        db.driver="org.postgresql.Driver"
        db.url="jdbc:postgresql://localhost:5432/exampledb?searchpath=public"
        db.user="postgres"
        db.password="postgres"
      }
    }
    

    3) You would change your application.test.conf to something like this:

    include "common.conf"
    
    slick.dbs {
      default {
        driver="slick.driver.H2Driver$"
        db.driver="org.h2.Driver"
        db.url="jdbc:h2:mem:test;MODE=PostgreSQL"
        db.username="sa"
        db.password=""
      }
    }
    

    Now the only thing is that you should rather have one set of evolutions (default) which is actually not that bad as it would make sure your test db is in sync with your production db (at least in terms of structure).

    It's not that above is the only solution. You could still have two differently named db configurations; you would need in such scenario to do some kind of remapping in your Guilce module (you would then have one module for prod and one for test - they could inherit from one another and only overwrite certain thing - like e.g. attaching test db in place of default one). It's basically a matter of taste.