I'm using play 2.3.8 and have some configuration in my GlobalSettings that change based on the mode of the application. So I have something like this:
object Global extends GlobalSettings {
override def onLoadConfig(config: Configuration, path: java.io.File, classloader: ClassLoader, mode: Mode.Mode) = {
println(mode)
val customConfig = //Based on mode.*
config ++ configuration ++ Configuration(ConfigFactory.parseMap(customConfig))
}
}
And then am trying to write tests to ensure that this behavior works:
class MyTest extends PlaySpec {
val testApp = FakeApplication(
additionalConfiguration = Map(
//SomeSettings And Stuff
"logger.application" -> "WARN",
"logger.root" -> "WARN"
)
)
val devApp = new FakeApplication(
additionalConfiguration = Map(
//SomeSettings And Stuff
"logger.application" -> "WARN",
"logger.root" -> "WARN"
)
) {
override val mode = Mode.Dev
}
val prodApp = new FakeApplication(
additionalConfiguration = Map(
//SomeSettings And Stuff
"logger.application" -> "WARN",
"logger.root" -> "WARN"
)
) {
override val mode = Mode.Prod
}
"ThisNonWorkingTestOfMine" must {
"when running application in test mode have config.thing = false" in running(testApp) {
assertResult(Mode.Test)(testApp.mode)
assertResult(false)(testApp.configuration.getBoolean("config.thing").get)
}
"when running application in dev mode have config.thing = false" in running(devApp) {
assertResult(Mode.Dev)(devApp.mode)
assertResult(false)(devApp.configuration.getBoolean("config.thing").get)
}
"when running application in prod mode have config.thing = true" in running(prodApp) {
assertResult(Mode.Prod)(prodApp.mode)
assertResult(true)(prodApp.configuration.getBoolean("config.thing").get)
}
}
}
And when I run these tests I see something a bit odd from my handy println:
Test
null
null
[info] MyTest:
[info] ThisNonWorkingTestOfMine
[info] play - Starting application default Akka system.
[info] play - Shutdown application default Akka system.
[info] - must when running application in test mode have config.thing = false
[info] play - Application started (Dev)
[info] - must when running application in dev mode have config.thing = false
[info] play - Application started (Prod)
[info] - must when running application in prod mode have config.thing = true *** FAILED ***
[info] Expected true, but got false (MyTest.scala:64)
[info] ScalaTest
How do I properly set the mode of the FakeApplication in Play 2.3? The way I have it now is based on a page from Mastering Play but clearly that isn't the way to go when using onLoadConfig it seems
Edit:
I'm also experimenting with OneAppPerTest and creating the FakeApplication in the newAppForTest method but it's still behaving oddly, with null's like the method above. This is really strange because if I set a random property like "foo" -> "bar" in the additionalConfiguration map when making my FakeApplication and then try to read it from config.getString in my Global object, it get's logged as None even though if I do app.configuration.getString in the test itself it shows bar. It feels like there is some type of disconnect here. And I don't get null for the mode if I use the FakeApplication.apply method rather than new FakeApplication
So I think this has something to do with the way that FakeApplication sets the mode to Mode.Test via override because if I copy the FakeApplication class and remove that line and create my own version of the class that let's me set the mode I have no issues. In other words, in my tests package I declare the following class:
package play.api.test
import play.api.mvc._
import play.api.libs.json.JsValue
import scala.concurrent.Future
import xml.NodeSeq
import play.core.Router
import scala.runtime.AbstractPartialFunction
import play.api.libs.Files.TemporaryFile
import play.api.{ Application, WithDefaultConfiguration, WithDefaultGlobal, WithDefaultPlugins }
case class FakeModeApplication(
override val path: java.io.File = new java.io.File("."),
override val classloader: ClassLoader = classOf[FakeModeApplication].getClassLoader,
val additionalPlugins: Seq[String] = Nil,
val withoutPlugins: Seq[String] = Nil,
val additionalConfiguration: Map[String, _ <: Any] = Map.empty,
val withGlobal: Option[play.api.GlobalSettings] = None,
val withRoutes: PartialFunction[(String, String), Handler] = PartialFunction.empty,
val mode: play.api.Mode.Value
) extends {
override val sources = None
} with Application with WithDefaultConfiguration with WithDefaultGlobal with WithDefaultPlugins {
override def pluginClasses = {
additionalPlugins ++ super.pluginClasses.diff(withoutPlugins)
}
override def configuration = {
super.configuration ++ play.api.Configuration.from(additionalConfiguration)
}
override lazy val global = withGlobal.getOrElse(super.global)
override lazy val routes: Option[Router.Routes] = {
val parentRoutes = loadRoutes
Some(new Router.Routes() {
def documentation = parentRoutes.map(_.documentation).getOrElse(Nil)
// Use withRoutes first, then delegate to the parentRoutes if no route is defined
val routes = new AbstractPartialFunction[RequestHeader, Handler] {
override def applyOrElse[A <: RequestHeader, B >: Handler](rh: A, default: A => B) =
withRoutes.applyOrElse((rh.method, rh.path), (_: (String, String)) => default(rh))
def isDefinedAt(rh: RequestHeader) = withRoutes.isDefinedAt((rh.method, rh.path))
} orElse new AbstractPartialFunction[RequestHeader, Handler] {
override def applyOrElse[A <: RequestHeader, B >: Handler](rh: A, default: A => B) =
parentRoutes.map(_.routes.applyOrElse(rh, default)).getOrElse(default(rh))
def isDefinedAt(x: RequestHeader) = parentRoutes.map(_.routes.isDefinedAt(x)).getOrElse(false)
}
def setPrefix(prefix: String) {
parentRoutes.foreach(_.setPrefix(prefix))
}
def prefix = parentRoutes.map(_.prefix).getOrElse("")
})
}
}
And then in my test I can use it like so:
val devApp = new FakeModeApplication(
additionalConfiguration = Map(
//SomeSettings And Stuff
"logger.application" -> "WARN",
"logger.root" -> "WARN"
), mode = Mode.Dev
)
And then the mode value comes through as what I set it to and not as null.
I'm posting this as an answer because it does solve the issue I'm facing, but I don't have an understanding of why the new keyword when making a FakeApplication like so: new FakeApplication() { override mode ... } causes the mode to come through as null in the onLoadConfig method on GlobalSettings. This feels like a hack rather than a solution and I'd appreciate if anyone with enough knowledge around this could post a solution that involves not copying the full FakeApplication class and changing one line.
Related
I want to create tests for my Play Framework Application and I continue getting java.lang.RuntimeException: There is no started application. I have an Asynchronous Controller like this:
class ComputerController #Inject()(computerService: ComputerService)(implicit executionContext: ExecutionContext){
def add = Action.async {
ComputerForm.form.bindFromRequest.fold(
errorForm => Future.successful(Ok(errorForm.toString)),
data => {
val ip = data.ip
val name = data.name
val user = data.user
val password = data.password
val computer = Computer(ip,name,user,password)
val futureTask = computerService.add(newComputer)
futureTask.map(res => Redirect(routes.HomeController.home()))
}
)
}
}
A helper trait for injecting:
trait Inject {
val injector = new GuiceApplicationBuilder()
.in(new File("conf/application.conf").
.in(Mode.Test)
.injector
}
And the tests is like this:
class ComputerControllerSpec extends PlaySpec with Inject with MockitoSugar with ScalaFutures {
lazy val computerService = mock[ComputerService]
when(computerService.add(any[Computer])) thenReturn Future.successful("Computer added")
implicit lazy val executionContext = injector.instanceOf[ExecutionContext]
val controller = new ComputerController(computerService)
"Computer Controller" should {
"add a new computer" in {
val computer = ComputerFormData("127.0.0.1","Computer","user","password")
val computerForm = ComputerForm.form.fill(computer)
val result = controller.add.apply {
FakeRequest()
.withFormUrlEncodedBody(computerForm.data.toSeq: _*)
}
val bodyText = contentAsString(result)
bodyText mustBe ""
}
}
}
I have also tried:
Initializing the executionContext implicit value with ExecutionContext.global instead and got java.lang.RuntimeException: There is no started application.
Adding with OneAppPerSuite to ComputerControllerSpec and got: akka.actor.OneForOneStrategy - Cannot initialize ExecutionContext; AsyncExecutor already shut down
Changing "add a new computer" in { for "add a new computer" in new WithApplication { and got: java.lang.RuntimeException: There is no started application
I don't really know how to inject that implicit ExecutionContext to my tests.
I guess you are missing "new WithApplication()"
Try this
class ComputerControllerSpec extends PlaySpec with Inject with MockitoSugar with ScalaFutures {
lazy val computerService = mock[ComputerService]
when(computerService.add(any[Computer])) thenReturn Future.successful("Computer added")
implicit lazy val executionContext = injector.instanceOf[ExecutionContext]
val controller = new ComputerController(computerService)
"Computer Controller" should {
"add a new computer" in new WithApplication() {
val computer = ComputerFormData("127.0.0.1","Computer","user","password")
val computerForm = ComputerForm.form.fill(computer)
val result = controller.add.apply {
FakeRequest()
.withFormUrlEncodedBody(computerForm.data.toSeq: _*)
}
val bodyText = contentAsString(result)
bodyText mustBe ""
}
}
}
The way this test got to work was:
Extending PlaySpec with MockitoSugar and BeforeAndAfterAll
Overwriting:
// Before all the tests, start the fake Play application
override def beforeAll() {
application.startPlay()
}
// After the tests execution, shut down the fake application
override def afterAll() {
application.stopPlay()
}
And then all the tests run without the thrown exception.
I'm not meaning the way play disabling PlayNettyServer and enable AkkaHttpServer which is described in the documentation by using:
lazy val root = (project in file("."))
.enablePlugins(PlayScala, PlayAkkaHttpServer)
.disablePlugins(PlayNettyServer)
I'm meaning that taking advantage of play framework's dependency injection and other tools like play-slick, and use akka-http directly in code like:
class AppInitiation #Inject()(implicit val system: ActorSystem, configuration: Configuration) {
implicit val materializer = ActorMaterializer()
implicit val timeout: Timeout = 5 seconds
val logger = Logger("Server")
val milkywayPath = path(Segment ~ RestPath)
val methods = get | put | post | delete
val gatewayExceptionHandler = ExceptionHandler {
case e: AskTimeoutException =>
complete(HttpResponse(InternalServerError, Nil, "Ask timeout!"))
case e: Exception =>
logger.error("unknown error", e)
complete(HttpResponse(InternalServerError, Nil, "Unknown error! Please contact administratro!"))
}
implicit def rejectionHandler = RejectionHandler.newBuilder()
.handle { case MissingHeaderRejection("X-Caller-Service") =>
complete(HttpResponse(BadRequest, Nil, "Missing required header X-Caller-Service!"))
}
.handle { case MissingQueryParamRejection("v") =>
complete(HttpResponse(BadRequest, Nil, "Missing required parameter v!"))
}
.result()
val requestHandler = system.actorOf(Props(new RequestHandler))
val routes = handleExceptions(gatewayExceptionHandler) {
(milkywayPath & methods & parameter('v.as[String]) & headerValueByName("X-Caller-Service") & extractRequest) {
(contextPath: String, resource: Path, v, caller, request) =>
complete {
val startTime = System.currentTimeMillis()
val callerInfo = CallerInfo(caller, contextPath, v, resource.toString())
val f = (requestHandler ? RequestHandlerMsg(callerInfo, request)).mapTo[HttpResponse]
f onComplete {
case r => accessLogger.info(s"method=${request.method.name} " +
s"uri=${request.uri} " +
s"caller=$caller " +
s"totalTime=${System.currentTimeMillis() - startTime}ms " +
s"resultStatus=${r.map(_.status.intValue).getOrElse(500)}")
}
f
}
}
}
val host = configuration.getString("gateway.host").getOrElse("localhost")
val port = configuration.getInt("gateway.port").getOrElse(9001)
Http().bindAndHandle(routes, host, port).onComplete(_ => logger.info(s"Server started listening request on port $port"))
}
and in module I can set it as:
override def configure(): Unit = {
bind(classOf[AppInitiation]).asEagerSingleton()
}
This works. But I still want to know how I can start it without PlayNettyServer running. I've tried:
lazy val `root` = (project in file("."))
.enablePlugins(PlayScala)
.disablePlugins(PlayNettyServer)
However there emerges the exception:
[info] p.a.l.c.ActorSystemProvider - Starting application default Akka system: application
play.core.server.ServerStartException: No ServerProvider configured with key 'play.server.provider'
at play.core.server.ServerProvider$$anonfun$1.apply(ServerProvider.scala:54)
at play.core.server.ServerProvider$$anonfun$1.apply(ServerProvider.scala:54)
I'm wondering if there is a way to take full advantage of Play framework with all its features and also build a higher performance server with akka http.
I want to write functional test for my controller in PlayFramework. To do that I want to mock implementation of some classes.
I found nice example of how to do that using spec2 here: http://www.innovaedge.com/2015/07/01/how-to-use-mocks-in-injected-objects-with-guiceplayscala/
But I'm using scala test with OneAppPerSuite trait that uses FakeApplication. Here are documentation:
https://www.playframework.com/documentation/2.4.x/ScalaFunctionalTestingWithScalaTest
Problem is that i cannot found a way to intercept into GuiceApplicationBuilder and override some bindings with mock implementation.
Here are FakeApplication implementation from play.api.test:
case class FakeApplication(
override val path: java.io.File = new java.io.File("."),
override val classloader: ClassLoader = classOf[FakeApplication].getClassLoader,
additionalPlugins: Seq[String] = Nil,
withoutPlugins: Seq[String] = Nil,
additionalConfiguration: Map[String, _ <: Any] = Map.empty,
withGlobal: Option[play.api.GlobalSettings] = None,
withRoutes: PartialFunction[(String, String), Handler] = PartialFunction.empty) extends Application {
private val app: Application = new GuiceApplicationBuilder()
.in(Environment(path, classloader, Mode.Test))
.global(withGlobal.orNull)
.configure(additionalConfiguration)
.bindings(
bind[FakePluginsConfig] to FakePluginsConfig(additionalPlugins, withoutPlugins),
bind[FakeRouterConfig] to FakeRouterConfig(withRoutes))
.overrides(
bind[Plugins].toProvider[FakePluginsProvider],
bind[Router].toProvider[FakeRouterProvider])
.build
So there is no way for me to intercept into GuiceApplicationBuilder and override bindings.
I'm new to playframework so sorry if question looks a bit silly.
Thanks!
Take a look at GuiceOneAppPerTest. Here is an example (Play 2.8, scala 2.13):
abstract class MyBaseSpec extends PlaySpec with GuiceOneAppPerTest with Results with Matchers with MockFactory {
def overrideModules: Seq[GuiceableModule] = Nil
override def newAppForTest(testData: TestData): Application = {
GuiceApplicationBuilder()
.overrides(bind[ControllerComponents].toInstance(Helpers.stubControllerComponents()))
.overrides(overrideModules: _*)
.build()
}
}
class OrderServiceSpec extends MyBaseSpec {
val ordersService: OrdersService = mock[OrdersService]
val usersService: UsersService = mock[UsersService]
override def overrideModules = Seq(
bind[OrdersService].toInstance(ordersService),
bind[UsersService].toInstance(usersService),
)
// tests
}
You are probably using an older version of ScalaTestPlus, which didn't support overriding FakeApplication with Application. In Play docs(Play 2.4) the library version is "1.4.0-M3" but it should be "1.4.0".
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)
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.)
-Hi. I'd like to embed Scala REPL with initialized environment into my app. I've looked at IMain class and it seems I could do it via instance of it. The instance is created and then stored into intp public var in process() of ILoop.
How can I bind some names and/or add some imports before process() (e.g. before REPL)?
Following code fails on line 3 because intp is not yet created (=> NPE):
val x = 3
val interp = new ILoop
interp.bind("x", x) // -> interp.intp.bind("x", x)
val settings = new Settings
settings.usejavacp.value = true
interp.process(settings)
Thank you-.
UPDATE: Overriding createInterpreter() unfortunately doesn't work:
val x = 3
val interp = new ILoop {
override def createInterpreter() {
super.createInterpreter()
intp.bind("x", x) // -> interp.intp.bind("x", x)
}
}
val settings = new Settings
settings.usejavacp.value = true
interp.process(settings)
Interpreter is stuck on input (looks like deadlock, happens only with code above):
x: Int = 3
Failed to created JLineReader: java.lang.NoClassDefFoundError: scala/tools/jline/console/completer/Completer
Falling back to SimpleReader.
Welcome to Scala version 2.9.2 (OpenJDK 64-Bit Server VM, Java 1.7.0_06-icedtea).
Type in expressions to have them evaluated.
Type :help for more information.
scala> println
<infinite_sleep>
Thanks dvigal for suggestion.
There is a github project called scala-ssh-shell which may do what you want, or at least get you closer.
-Hi, sorry I not Scala REPL hacker but i think you can do something like:
class YourILoop(in0: Option[BufferedReader], protected override val out: JPrintWriter)
extends ILoop(in0, out) {
override def createInterpreter() {
if (addedClasspath != "")
settings.classpath append addedClasspath
intp = new ILoopInterpreter
val x = 3;
intp.bind("x", x)
}
}
object Run {
def errorFn(str: String): Boolean = {
Console.err println str
false
}
def process(args: Array[String]): Boolean = {
val command = new GenericRunnerCommand(args.toList, (x: String) => errorFn(x))
import command.{ settings, howToRun, thingToRun }
new YourILoop process settings
}
def main(args: Array[String]) {
process(args)
}
}