I have a controller that ask
s an actor before answering and two test cases:
play test
the second test failsplay testOnly ApplicationSpec
and play testOnly IntegrationSpec
, both succeedI think that the Akka system is shut down by the first test and not started again by the second test, but why ? And how can I work around that ?
The controller:
object Application extends Controller {
implicit val _ = Timeout(3 seconds)
val gamesManagerRef = Akka.system().actorOf(GamesManager.props)
def index = Authenticated.async { implicit request =>
(gamesManagerRef ? GamesManager.ListWaitingGames).map {
case GamesManager.MultipleOperationOk(games) =>
Ok(views.html.index(GameInformation.getWaitings(request.jedis)))
}
}
}
The unit test:
class ApplicationSpec extends Specification {
"Application" should {
"send 404 on a bad request" in new WithApplication{
route(FakeRequest(GET, "/boum")) must beNone
}
"render the index page" in new WithApplication{
val home = route(FakeRequest(GET, "/")).get
status(home) must equalTo(OK)
contentType(home) must beSome.which(_ == "text/html")
contentAsString(home) must contain ("jumbotron")
}
}
}
The integration test :
class IntegrationSpec extends Specification {
"Application" should {
"work from within a browser" in new WithBrowser {
browser.goTo("http://localhost:" + port)
browser.pageSource must contain("jumbotron")
}
}
}
The tests are pretty similar to the default ones when generated by play new
Content given when both are executed :
play.api.Application$$anon$1: Execution exception[[AskTimeoutException: Recipient[Actor[akka://application/user/$a#1274766555]] had already been terminated.]]
at play.api.Application$class.handleError(Application.scala:293) ~[play_2.10.jar:2.2.1]
at play.api.test.FakeApplication.handleError(Fakes.scala:203) ~[play-test_2.10.jar:2.2.1]
at play.core.server.netty.PlayDefaultUpstreamHandler$$anonfun$12$$anonfun$apply$1.applyOrElse(PlayDefaultUpstreamHandler.scala:165) ~[play_2.10.jar:2.2.1]
at play.core.server.netty.PlayDefaultUpstreamHandler$$anonfun$12$$anonfun$apply$1.applyOrElse(PlayDefaultUpstreamHandler.scala:162) ~[play_2.10.jar:2.2.1]
at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:33) ~[scala-library.jar:na]
at scala.util.Failure$$anonfun$recover$1.apply(Try.scala:185) ~[scala-library.jar:na]
Caused by: akka.pattern.AskTimeoutException: Recipient[Actor[akka://application/user/$a#1274766555]] had already been terminated.
at akka.pattern.AskableActorRef$.ask$extension(AskSupport.scala:134) ~[akka-actor_2.10.jar:2.2.0]
at akka.pattern.AskableActorRef$.$qmark$extension(AskSupport.scala:146) ~[akka-actor_2.10.jar:2.2.0]
at controllers.Application$$anonfun$index$1.apply(Application.scala:24) ~[classes/:na]
at controllers.Application$$anonfun$index$1.apply(Application.scala:23) ~[classes/:na]
at controllers.Application$Authenticated$$anonfun$invokeBlock$3.apply(Application.scala:74) ~[classes/:na]
at controllers.Application$Authenticated$$anonfun$invokeBlock$3.apply(Application.scala:69) ~[classes/:na]
What gives the error page generated by Play! to the test which do not contain my "jumbotron"
I tried to create new FakeApplication
to give in the WithBrowser
constructor, but only empty page happens.
Full code source available: https://github.com/Isammoc/yinyang/tree/8cf8ad625b7ef35423f17503a2a35fe390352d22
The problem is that you are holding a reference to games manager actor ref in a val in an object. The first test that runs will initialise this object, which will get the currently running actor system and lookup the the actor. Then the actor system will be shutdown, and that actor ref will become invalid. But the application controller still holds the invalid ref in the val, so when the next test runs, it uses the invalid actor ref from the shut down actor system.
Here are the ways you can solve it:
gamesManagerRef
to a def
, use actorFor
to look it up, and create it (using actorOf
) in Global.onStart
. So the actor is created once in Global.onStart
, and the lookup is done every time it's used.gamesManagerRef
a @volatile var
in some other object, and have your Global.onStart
method create the actor and then assign it to that var
. In this way, the lookup doesn't have to be done each time it's used, but it's kind of ugly because you have this global shared mutable state.Update 23/07/2018: The above suggestions are completely out of date. The recommended approaches are well documented in https://www.playframework.com/documentation/2.6.x/ScalaAkka.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With