使用Slick和Postgres进行Play 2.1单元测试

5

我希望能够运行Play 2 Scala应用程序的单元测试,并使用与生产环境中相同的数据库设置:Slick和Postgres。但以下代码在第二个测试时失败,报错为“java.sql.SQLException: Attempting to obtain a connection from a pool that has already been shutdown.”。

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
    }
  }
}

我这样运行测试:

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

还有两个谜团:

1)所有测试都运行了,即使我只要求运行BogusTest

2)始终使用application.conf而不是def.conf,并且驱动程序信息来自application.conf而不是在代码中配置的信息。


请参考https://dev59.com/QWUp5IYBdhLWcg3wRF6c,了解如何使用“test”指定不同的配置文件。 - James Ward
谢谢,看起来这应该解决谜团#2。你能否也揭示一下主要问题? - Mike Slinn
我也遇到了同样的问题。甚至没有使用specs2的Around特性,只是在in{ running(fakeApp) {Database.forDataSource(DB.getDataSource()) withSession {}} }中包装,也会出现这个异常。 - Meredith
我认为我有你需要的答案,但请发布一个包含h2和所有依赖项的Github项目,以便在向论坛发送垃圾信息之前进行测试 :) - Edmondo
可能启用开发配置中的日志记录并发布测试运行的日志将会很有帮助。 - Jean
1个回答

4
这是一个暂定的答案,因为我目前在play 2.2.0上进行了测试,使用MYSQL数据库,无法复现您的错误。
我感觉您的代码中可能有一个非常棘手的错误。首先,如果您探索Play提供的DBPlugin实现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)
    }
  }

你会发现onStop()方法关闭了连接池。因此,很明显,你为第二个测试示例提供的应用程序已经停止(因此其插件已停止并且数据库连接池已关闭)。

Scalatests和specs2可以并行运行测试,并且你可以依赖于测试助手,因为它是线程安全的:

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

然而,当你执行时,
DB.getDataSource("test")

从 Play 的源代码中:

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

在这里有一个隐式的问题,它没有解析成FakeApplication(它不是范围内的隐式变量!),而是解析成Play.current。在第二种情况下,似乎这不是你想要的情况,Play.current仍然指向之前的FakeApplication实例:这可能取决于隐含在闭包中的方式。但如果你重构fakeApp方法,你可以确保刚刚创建的应用程序用于解析隐式(你总是可以将隐式参数的值显式化)。
  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 }
      }
  }

请使用我在这里所做的更改再试一次 - https://gist.github.com/drstevens/8048116/f181c671c64df210e37197a30a5801e25f6014fc - drstevens
我没有你的model.training._全部内容,请提供一个可以克隆调试的独立项目在github上。目前你所提供的唯一信息无法让我进一步帮助你,因为我无法运行你的代码。 - Edmondo
drstevens,您的建议也得出了相同的结果。 - Mike Slinn
1
正如我所说,进一步进行调查的唯一方法是您提供一个 Github 存储库。 - Edmondo
@MikeSlinn,最近怎么样? - Meredith
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接