Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play ! 2.2.4 / Akka : tests failed when run together, but ok separately

I have a controller that asks an actor before answering and two test cases:

  • When I run play test the second test fails
  • When I run play testOnly ApplicationSpec and play testOnly IntegrationSpec, both succeed

I 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

like image 271
Isammoc Avatar asked Jan 02 '14 17:01

Isammoc


1 Answers

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:

  • Change 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.
  • Make 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.
  • My preferred solution is to write a plugin (http://developer.vz.net/2012/03/16/writing-a-play-2-0-module/) that looks up the actor ref when the plugin starts, or puts it in a lazy val, and then have consumers access the actor ref through doing a plugin lookup.

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.

like image 183
James Roper Avatar answered Sep 20 '22 02:09

James Roper