Search code examples
scalaplayframeworkslickspecs2

Having Trouble setting up in memory db for unit tests in play 2.4 an Slick 3


I am trying to set up an in Memory h2 db, and then create the necessary tables using Slicks features, for use in my play specs2 tests. I have created an abstract class that overrides WithApplication and then overrides the around method but I get an error when the createTables function tries to run pre tests. I suspect that I am not passing in the database connection / config correctly but the error I am getting is not very helpful. I am creating the db by wrapping plays Databases.withInMemory function

Here is my db helper object

package testHelpers

import models.VisitorImages._
import models.VisitorRegistrations._
import models.VisitorSignatures._
import models.VisitorStatuses._
import models.Visitors._
import org.specs2.execute.{Result, AsResult}
import play.api.test.WithApplication
import play.api.{Logger, Application, Play}
import play.api.db.slick.DatabaseConfigProvider
import play.api.db.{Databases, Database}
import slick.backend.DatabaseConfig
import slick.profile.{BasicProfile, RelationalProfile}

import scala.concurrent.Await
import scala.concurrent.duration.Duration

object TestDatabase {
  class testDbProvider extends DatabaseConfigProvider {
    def get[P <: BasicProfile]: DatabaseConfig[P] = {
      DatabaseConfigProvider.get("default")(Play.current)
    }
  }

  def startTestDb[T](block: Database => T) = {
    Databases.withInMemory(
      name = "testDB",
      urlOptions = Map(
        "MODE" -> "MSSQLServer"
      ),
      config = Map(
        "logStatements" -> true
      )
    )(block)
  }

  abstract class WithTestDb extends WithApplication((TestApplication.application)) {
    val profile = slick.driver.H2Driver
    import profile.api._

    override def around[T: AsResult](t: => T): Result = super.around {
      setupData()
      t
    }

    val allTables = (VisitorSignatures.schema ++ VisitorImages.schema ++ Visitors.schema ++ VisitorStatuses.schema ++ VisitorRegistrations.schema
    ).create

    /** Create all tables in database */
    def createTables = {
      Logger.info(s"yadda yadda yadda")
      val db = new testDbProvider().get.db
      Await.result(db.run(Visitors.schema.create), Duration.Inf)
      Logger.info(s"variable user is blah blah")
    }

    /** Delete all tables in database */
    def drop = {
//      allTables.drop
    }

    def setupData() {
      // setup data
      createTables
    }
  }

}

Here is how I am trying to use it

package unit.models

import models.VisitorRegistrations._
import play.api.{Play, db}
import play.api.db.slick.DatabaseConfigProvider
import play.api.test.{WithApplication, PlaySpecification}
import play.db.NamedDatabase
import providers.VisitorRegistrationProvider
import repositories.VisitorRegistrationRepository
import slick.driver.JdbcProfile
import slick.lifted.TableQuery
import testHelpers.TestDatabase.WithTestDb
import testHelpers.{Inject, TestApplication, TestDatabase}

import scala.concurrent.Await
import scala.concurrent.duration.Duration

class VisitorRegistrationsSpec extends PlaySpecification with Inject {
  val vrp = inject[VisitorRegistrationProvider]
  val db = vrp.dbConfig.db

  import vrp.dbConfig.driver.api._

  TestDatabase.startTestDb { database =>
    "VisitorResgistrations" should {
      "save a vr" in new WithTestDb {

        val res = Await.result(db.run(VisitorRegistrations.result), Duration.Inf)

        res must equalTo(1)
      }
    }

  }
}

And here is the error I get

[info] VisitorResgistrations should
[error]   ! save a vr
[error]    null (Database.scala:61)
[error] testHelpers.TestDatabase$WithTestDb.createTables(Database.scala:61)
[error] testHelpers.TestDatabase$WithTestDb.setupData(Database.scala:72)
[error] testHelpers.TestDatabase$WithTestDb$$anonfun$around$1.apply(Database.scala:50)
[error] play.api.test.WithApplication$$anonfun$around$2.apply(Specs.scala:39)
[error] play.api.test.WithApplication$$anonfun$around$2.apply(Specs.scala:39)
[error] play.api.test.PlayRunners$class.running(Helpers.scala:42)
[error] play.api.test.Helpers$.running(Helpers.scala:363)
[error] play.api.test.WithApplication.around(Specs.scala:39)
[error] testHelpers.TestDatabase$WithTestDb.around(Database.scala:49)
[error] play.api.test.WithApplication.delayedInit(Specs.scala:36)
[error] testHelpers.TestDatabase$WithTestDb.<init>(Database.scala:45)
[error] unit.models.VisitorRegistrationsSpec$$anonfun$1$$anonfun$apply$2$$anonfun$apply$3$$anon$1.<init>(VisitorRegistrationsSpec.scala:30)
[error] unit.models.VisitorRegistrationsSpec$$anonfun$1$$anonfun$apply$2$$anonfun$apply$3.apply(VisitorRegistrationsSpec.scala:30)
[error] unit.models.VisitorRegistrationsSpec$$anonfun$1$$anonfun$apply$2$$anonfun$apply$3.apply(VisitorRegistrationsSpec.scala:30)

Solution

  • Off-hand, it looks as if you have an initialization order problem. Try changing your class-level vals to lazy val or def.