5

I want to run unit tests for a Play 2 Scala app using the same database setup as used in production: Slick with Postgres. The following fails with "java.sql.SQLException: Attempting to obtain a connection from a pool that has already been shutdown." on the 2nd test.

package controllers

import org.specs2.mutable._
import play.api.db.DB
import play.api.Play.current
import play.api.test._
import play.api.test.Helpers._
import scala.slick.driver.PostgresDriver.simple._

class BogusTest extends Specification {

  def postgresDatabase(name: String = "default", 
                       options: Map[String, String] = Map.empty): Map[String, String] =
    Map(
      "db.test.driver"   -> "org.postgresql.Driver",
      "db.test.user"     -> "postgres",
      "db.test.password" -> "blah",
      "db.test.url"      -> "jdbc:postgresql://localhost/blah"
    )

  def fakeApp[T](block: => T): T =
    running(FakeApplication(additionalConfiguration = 
      postgresDatabase("test") ++ Map("evolutionplugin" -> "disabled"))) {
        def database = Database.forDataSource(DB.getDataSource("test"))
        database.withSession { implicit s: Session => block }
      }

  "Fire 1" should {
    "do something" in fakeApp {
      success
    }
  }

  "Fire 2" should {
    "do something else" in fakeApp {
      success
    }
  }
}

I run the test like this:

$ play -Dconfig.file=`pwd`/conf/dev.conf "test-only controllers.BogusTest"

Two other mysteries:

1) All tests run, even though I ask for just BogusTest to run

2) application.conf is always used, not def.conf, and the driver information comes from application.conf, not the info configured in the code.

5
  • See stackoverflow.com/questions/15399161/… for info on how to specify a different config file with test.
    – James Ward
    Commented Jun 30, 2013 at 18:07
  • Thanks, looks like this should take care of mystery #2. Can you shed light on the main issue as well?
    – Mike Slinn
    Commented Jun 30, 2013 at 21:56
  • I'm running into the same issue too. Even without using the Around trait from specs2, and simply wrapping within in{ running(fakeApp) {Database.forDataSource(DB.getDataSource()) withSession {}} } gives this exception too.
    – Meredith
    Commented Dec 18, 2013 at 23:42
  • I think I have the answer for you, but please publish a github project with h2 and all the dependencies so I can test before spamming the forum :)
    – Edmondo
    Commented Dec 19, 2013 at 10:38
  • it would probably be helpful to enable logging in the dev conf then publish the logs of the test run.
    – Jean
    Commented Dec 19, 2013 at 12:19

1 Answer 1

4
+50

This is a tentative answer as I have currently tested on play 2.2.0 and I can't reproduce your bug, using a MYSQL database.

I feel there might be a very tricky bug in your code. First of all, if you explore the DBPlugin implementation provided by Play, BoneCPPPlugin:

  /**
   * Closes all data sources.
   */
  override def onStop() {
    dbApi.datasources.foreach {
      case (ds, _) => try {
        dbApi.shutdownPool(ds)
      } catch { case NonFatal(_) => }
    }
    val drivers = DriverManager.getDrivers()
    while (drivers.hasMoreElements) {
      val driver = drivers.nextElement
      DriverManager.deregisterDriver(driver)
    }
  }

You see that the onStop() method closes the connection pool. So it's clear, you are providing to the second test example an application which has already been stopped (and therefore its plugins are stopped and the db connectin pool closed).

Scalatests and specs2 run the test in parallel, and you can rely on the test helper because it's thread-safe:

  def running[T](fakeApp: FakeApplication)(block: => T): T = {
        synchronized {
          try {
            Play.start(fakeApp)
            block
          } finally {
            Play.stop()
            play.api.libs.ws.WS.resetClient()
          }
        }
      }

However, when you do

DB.getDataSource("test")

From the source code of Play:

  def getDataSource(name: String = "default")(implicit app: Application): DataSource = app.plugin[DBPlugin].map(_.api.getDataSource(name)).getOrElse(error)

So here there is an implicit, does which not get resolved to FakeApplication (it is not an implicit in scope!!!), but to Play.current and it appears that in the second case, this is not what you were expecting it to be, Play.current still point to the previous instance of FakeApplication: it probably depends on how implicit are captured in closures

If you however, refactor the fakeApp method, you can ensure the application you just created is used to resolve the implicit (you can always make explicit the value for an implicit parameter)

  def fakeApp[T](block: => T): T = {
    val fakeApplication = FakeApplication(additionalConfiguration =
      postgresDatabase("test") ++ Map("evolutionplugin" -> "disabled"))
      running(fakeApplication) {
        def database = Database.forDataSource(DB.getDataSource("test")(fakeApplication))
        database.withSession { implicit s: Session => block }
      }
  }
9
  • Edmondo, excellent sleuthing! This does move things forward. I now get "RuntimeException: There is no started application" when running from the command line, unfortunately. I made a gist with all the relevant files gist.github.com/mslinn/8046542
    – Mike Slinn
    Commented Dec 19, 2013 at 21:32
  • can you provide an sbt project on a github repo? I am missing application.conf which is imported in test.conf and would be tooh ard to debug. In your gist I see you still have play.api.Play.current as an import. I would remove it to see what happens
    – Edmondo
    Commented Dec 19, 2013 at 22:57
  • Try again with the change I made here - gist.github.com/drstevens/8048116/…
    – drstevens
    Commented Dec 19, 2013 at 23:41
  • I don't have all your model.training._ , please provide a stand-alone project on github I can clone to debug. I will never be able to help you further with the only information you are providing right now because I can't run your code
    – Edmondo
    Commented Dec 20, 2013 at 11:45
  • 1
    as I said the only way to go further with the investigation is for you to provide a github repo
    – Edmondo
    Commented Dec 20, 2013 at 13:40

Not the answer you're looking for? Browse other questions tagged or ask your own question.