Play 报告关闭后无法获取 ClosableLazy 值

Play reports that it can't get ClosableLazy value after it has been closed

我正尝试在 Play/Scala/ReactiveMongo 项目中进行 运行 规范测试。设置是这样的:

class FeaturesSpec extends Specification {

  "Features controller" should {
    "create feature from JSON request" in withMongoDb { app =>
      // do test
    }
}

使用MongoDbFixture如下:

object MongoDBTestUtils {
  def withMongoDb[T](block: Application => T): T = {
    implicit val app = FakeApplication(
      additionalConfiguration = Map("mongodb.uri" -> "mongodb://localhost/unittests")
    )
    running(app) {
      def db = ReactiveMongoPlugin.db
      try {
        block(app)
      } finally {
        dropAll(db)
      }
    }
  }

  def dropAll(db: DefaultDB) = {
    Await.ready(Future.sequence(Seq(
      db.collection[JSONCollection]("features").drop()
    )), 2 seconds)
  }
}

测试 运行s 时,日志非常嘈杂,抱怨资源已经关闭。虽然测试工作正常,但这很奇怪,我想知道为什么会发生这种情况以及如何解决它。

错误:

[info] application - ReactiveMongoPlugin stops, closing connections...
[warn] play - Error stopping plugin
java.lang.IllegalStateException: Can't get ClosableLazy value after it has been closed
    at play.core.ClosableLazy.get(ClosableLazy.scala:49) ~[play_2.11-2.3.7.jar:2.3.7]
    at play.api.libs.concurrent.AkkaPlugin.applicationSystem(Akka.scala:71) ~[play_2.11-2.3.7.jar:2.3.7]
    at play.api.libs.concurrent.Akka$$anonfun$system.apply(Akka.scala:29) ~[play_2.11-2.3.7.jar:2.3.7]
    at play.api.libs.concurrent.Akka$$anonfun$system.apply(Akka.scala:29) ~[play_2.11-2.3.7.jar:2.3.7]
    at scala.Option.map(Option.scala:145) [scala-library-2.11.4.jar:na]

异常说明您在应用停止后使用ReactiveMongo插件。

您可能想尝试使用 Around:

class withMongoDb extends Around with Scope {
  val db = ReactiveMongoPlugin.db

  override def around[T: AsResult](t: => T): Result = try {
    val res = t
    AsResult.effectively(res)
  } finally {
    ...
  }
}

您还应该看看 Flapdoodle Embedded Mongo,这样您就不必在测试 IIRC 后删除数据库。

出现此问题的原因可能是您的测试练习代码引用了已关闭的 MongoDB 实例。每次运行 Play Specs2 测试后,MongoDb 连接都会重置,因此您的第一个测试可能会通过,但后续测试可能会持有对已关闭实例的陈旧引用,因此会失败。

解决此问题的一种方法是确保您的应用程序满足以下条件:

  • 避免对 MongoDb 数据库资源使用 val 或 lazy val
  • (重新)在应用程序启动时初始化所有数据库引用。

我写了一个 blog post 描述了在 Play Controller 的上下文中解决问题的方法。