根据 IO 和 TestActorRef

Akka IO and TestActorRef

如果我需要通过 spray-can 编写涉及 HTTP 请求的集成测试,我如何确保 spray-can 使用 CallingThreadDispatcher?

目前以下演员将打印None

class Processor extends Actor {
  override def receive = {
    case Msg(n) =>
      val response = (IO(Http) ? HttpRequest(GET, Uri("http://www.google.com"))).mapTo[HttpResponse]
      println(response.value)
  }
}

如何确保请求与测试在同一线程上执行(导致同步请求)?

这似乎是进行集成内部测试的奇怪方式,因为您不模拟 "Google",所以更像是集成外部测试和同步 TestActorRef 不太适合这里.在 spray 中控制线程的要求也很棘手。但是,如果您真的需要它来进行 http-request - 这是可能的。在一般情况下,您必须在 application.conf:

中设置多个调度程序
  • "manager-dispatcher"(来自 Http.scala)派遣您的 IO(Http) ? req
  • "host-connector-dispatcher" 由 HttpHostConnector(或 ProxyHttpHostConnector)实际发送您的请求
  • "settings-group-dispatcher" 对于 Http.Connect

它们都在Configuration Section of spray documentation. And they all are pointing to "akka.actor.default-dispatcher" (see Dispatchers)中描述,所以你可以通过改变这个来改变它们。

这里的问题是调用线程实际上不能保证是你的线程,所以它对你的测试没有多大帮助。试想一下,如果一些参与者注册了一些处理程序,响应您的消息:

  //somewhere in spray...
  case r@Request => registerHandler(() => {
      ...
      sender ! response
  })

响应可能是从另一个线程发送的,所以response.value在当前可能仍然是None。实际上,响应将从底层套接字库的侦听线程发送,独立于您的测试线程。简单地说,请求可能在一个(您的)线程中发送,但响应在另一个线程中接收。

如果您真的真的需要在这里阻塞,我建议您将此类代码示例(如 IO(Http) ? HttpRequest)移出并在测试中以任何方便的方式模拟它们。像那样发短信:

trait AskGoogle {
   def okeyGoogle = IO(Http) ? HttpRequest(GET, Uri("http://www.google.com"))).mapTo[HttpResponse] 
}

trait AskGoogleMock extends AskGoogle {
   def okeyGoogle = Await.result(super.okeyGoogle, timeout)
}

class Processor extends Actor with AskGoogle {
  override def receive = {
    case Msg(n) =>
      val response = okeyGoogle
      println(response.value)
  }
}

val realActor = system.actorOf(Props[Processor])
val mockedActor = TestActorRef[Processor with AskGoogleMock]

顺便说一句,您可以使用另一个 TestActorRef 模拟 IO(HTTP) 到自定义 actor,它将为您执行外部请求 - 如果您有一个大项目,它应该需要最少的代码更改.