Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Play no application started when grabbing data sources from application.conf

I am trying to read in data sources from my application.conf file, but every time I run my server, or try and run test cases, I am getting an error saying that there is no application started.

Here is an example of what I am trying to do:

Unit test that is trying to read a property from my application.conf

class DbConfigWebUnitTest extends PlaySpec with OneAppPerSuite {

  implicit override lazy val app: FakeApplication = FakeApplication(
    additionalConfiguration = Map("db.test.url" -> "jdbc:postgresql://localhost:5432/suredbitswebtest",
      "db.test.user" -> "postgres", "db.test.password" -> "postgres", "db.test.driver" -> "org.postgresql.Driver"))
  val dbManagementWeb = new DbManagementWeb with DbConfigWeb with DbTestQualifier
  "DbConfigWebTest" must {
    "have the same username as what is defined in application.conf" in {
      dbManagementWeb.username must be("postgres")
    }
  }
}

Here is my DbConfigWeb

import play.api.Play.current    
trait DbConfigWeb extends DbConfig { qualifier: DbQualifier =>

      val url: String = current.configuration.getString(qualifier + ".url").get
      val username: String = current.configuration.getString(qualifier + ".user").get
      val password: String = current.configuration.getString(qualifier + ".password").get
      val driver: String = current.configuration.getString(qualifier + ".driver").get
      override def database: DatabaseDef = JdbcBackend.Database.forURL(url, username, password, null, driver)
      override implicit val session = database createSession
    }

    trait DbQualifier {
      val qualifier: String
    }

    trait DbProductionQualifier extends DbQualifier {
      override val qualifier = "db.production"
    }

    trait DbTestQualifier extends DbQualifier {
      override val qualifier = "db.test"
    }

and lastly here is my stack trace:

[suredbits-web] $ last test:test
[debug] Forking tests - parallelism = false
[debug] Create a single-thread test executor
[debug] Runner for sbt.FrameworkWrapper produced 0 initial tasks for 0 tests.
[debug] Runner for org.scalatest.tools.Framework produced 2 initial tasks for 2 tests.
[debug]   Running TaskDef(com.suredbits.web.db.DbConfigWebUnitTest, sbt.ForkMain$SubclassFingerscan@48687c55, false, [SuiteSelector])
[error] Uncaught exception when running com.suredbits.web.db.DbConfigWebUnitTest: java.lang.RuntimeException: There is no started application
sbt.ForkMain$ForkError: There is no started application
    at scala.sys.package$.error(package.scala:27)
    at play.api.Play$$anonfun$current$1.apply(Play.scala:71)
    at play.api.Play$$anonfun$current$1.apply(Play.scala:71)
    at scala.Option.getOrElse(Option.scala:120)
    at play.api.Play$.current(Play.scala:71)
    at com.suredbits.web.db.DbConfigWeb$class.$init$(DbConfigWebProduction.scala:14)
    at com.suredbits.web.db.DbConfigWebUnitTest$$anon$1.<init>(DbConfigWebUnitTest.scala:14)
    at com.suredbits.web.db.DbConfigWebUnitTest.<init>(DbConfigWebUnitTest.scala:14)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at java.lang.Class.newInstance(Class.java:379)
    at org.scalatest.tools.Framework$ScalaTestTask.execute(Framework.scala:641)
    at sbt.ForkMain$Run$2.call(ForkMain.java:294)
    at sbt.ForkMain$Run$2.call(ForkMain.java:284)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
like image 940
Chris Stewart Avatar asked Feb 07 '15 20:02

Chris Stewart


1 Answers

I think the key problem is that vals in Scala traits are initialized at construction time, which is prior to the test Play application being started (presumably its lifecycle is tied to each spec example.) You have a couple of workarounds:

  • make everything in DbConfigWeb a def or perhaps a lazy val
  • give DbConfigWeb an abstract play.api.Application field from which to extract the config values (rather than using current) and pass it explicitly (the fake application) to whatever DbManagementWeb is as a constructor parameter

Here's a simplified version, using the first approach (which works for me):

import play.api.Play.current

trait DbConfig

trait DbConfigWeb extends DbConfig {
  self: DbQualifier =>

  // Using defs instead of vals
  def url: String = current.configuration.getString(qualifier + ".url").get
  def username: String = current.configuration.getString(qualifier + ".user").get
  def password: String = current.configuration.getString(qualifier + ".password").get
  def driver: String = current.configuration.getString(qualifier + ".driver").get
}

trait DbQualifier {
  val qualifier: String
}

trait DbTestQualifier extends DbQualifier {
  override val qualifier = "db.test"
}

and the spec:

import controllers.{DbConfigWeb, DbTestQualifier}
import org.scalatestplus.play.{OneAppPerSuite, PlaySpec}
import play.api.test.FakeApplication

class DbConfigTest extends PlaySpec with OneAppPerSuite {
  implicit override lazy val app: FakeApplication = FakeApplication(
    additionalConfiguration = Map("db.test.url" -> "jdbc:h2:mem:play",
      "db.test.user" -> "sa", "db.test.password" -> "", "db.test.driver" -> "org.h2.Driver"))
  val dbManagementWeb = new DbConfigWeb with DbTestQualifier
  "DbConfigWebTest" must {
    "have the same username as what is defined in application.conf" in {
      dbManagementWeb.username must be("sa")
    }
  }
}

Personally I prefer the second approach, which keeps the application state passed around explicitly rather than relying on play.api.Play.current, which you cannot rely on always being started.

You mentioned in the comments that lazy vals were not working for you but I can only conjecture that some chain of calls was forcing initialization: check again that this isn't the case.

Note also that order of initialization for vals can be complex and, while some might disagree, it's a pretty safe bet to stick to defs as trait members unless you're sure it's some expensive operation (in which case a lazy val might be an option.)

like image 98
Mikesname Avatar answered Nov 01 '22 16:11

Mikesname