如何测试为Play框架注入akka系统的控制器?

时间:2015-10-08 16:04:39

标签: scala unit-testing playframework controller

以下是我的控制器:

package controllers

import java.util.TimeZone

import akka.actor.{ActorNotFound, ActorSystem}
import akka.util.Timeout
import com.google.inject.Inject
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
import play.api.Logger
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.json.{JsValue, JsError, JsSuccess}
import play.api.mvc._

import scala.concurrent.Future
import scala.concurrent.duration._

class ScheduleController @Inject()(system: ActorSystem) extends Controller {

  val scheduler = QuartzSchedulerExtension.get(system)
  implicit val timeout = new Timeout(5.seconds)

  def index = Action.async {
    Future.successful(Ok("hi"))
  }

  def run = Action.async { request =>
    // find actor and "start" it by sending message to it, return Ok() if found
    // if actor is not found, return BadRequest()
    // if error return InternalServerError()
  }
}

我一直在寻找教程,但大多数都已经过时,因为他们处理Play 2.3。

1 个答案:

答案 0 :(得分:0)

我解决了与Play2控制器和演员非常相似的问题,除了在我的情况下我没有向我的控制器中注入ActorSystem。相反,我使用Play的默认actor系统,并在我的应用程序启动时从我的全局设置对象创建actor。对于给定的actor,我在其伴随对象中提供了一个选择方法。例如:

class MyActor extends Actor {
  def receive = {
    case Request => sender ! Response
  }
}

object MyActor {
  val path = // assuming fixed actor path
  val sys  = // reference to play actor system
  def select: ActorSelection = sys.actorSelection(path)

  // my actor messages
  case object Request
  case object Response
}

我有这个actor设置的方式我计划使用Request从我的控制器方法中询问它,并期望从它处理一个Response对象。在此方案中,控制器是非参与者sender。通过在actor选择上调用ask,我将关闭未来并将结果映射到与预期响应匹配的部分函数。这是我的控制器的样子:

class MyController extends Controller {

  implicit val timeout = // set your timeout duration

  def index = Action.async {
    MyActor.select ? Request map {
      case Response => Ok("success")
    }
  }
}

测试此控制器端点非常具有挑战性,因为您的actor可能尚未处于就绪状态以开始处理消息。我花了大约两天的时间试图弄清楚如何使用我通过其伴侣对象暴露的演员选择来正确阻挡我演员的生命周期状态。我最终在我的测试类中做的是创建一个方法,它接受一个函数参数并循环我的actor状态,直到我可以解析它的ActorRef。我是ScalaTest的粉丝,所以这就是我的单元测试的结果:

class MySpec extends FlatSpec with Matchers with BeforeAndAfterAll {

  implicit val fakeApp: FakeApplication = new FakeApplication()
  override def beforeAll() = Play.start(fakeApp)
  override def afterAll() = Play.stop(fakeApp)

  def awaitableActorTest(assertions: => Any): Unit = {
    val timeout = 1.second
    var isActorReady = false
    while(!isActorReady) {
      val futureRef = Await.ready(MyActor.select.resolveOne(timeout), timeout)
      futureRef.value.get match {
        case Success(_) =>
          assertions
          isActorReady = true
        case Failure(_) =>
          Thread.sleep(2000)
      }
    }
  }

  it should "process actor request and response via controller endpoint" in {
    awaitableActorTest {
      val result = route(routes.MyController.index).get
      status(result) shouldBe OK
      contentAsString(result) shouldBe "success"
    }
  }

}

我从这个awaitableActorTest模式中获得的是一种非常干净的方式,只有当我的actor处于活动状态且可用于处理消息时才能可靠地命中我的控制器端点。如果不是,我的ActorRef使用其选择伙伴将来不会成功解决 - 而是以失败告终。当它失败时,我会睡几秒钟并重复循环。当我选择成功解析ActorRef时,我打破了循环。

我不熟悉您在代码段中引用的QuartzSchedulerExtension,但我希望我的示例很有用。