scalaakkasprayspray-client

Akka IO and TestActorRef


If I need to write an integration test involving HTTP request via spray-can, how can I make sure that spray-can is using CallingThreadDispatcher?

Currently the following actor will print 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)
  }
}

How can I make sure that the request is being performed on the same thread as the test (resulting in a synchronous request)?


Solution

  • It seems like strange way to do integration internal-testing as you don't mock the "Google", so is more like integration external-testing and synchronous TestActorRef doesn't much fit here. The requirement to control threads inside spray is also pretty tricky. However, if you really need that for http-request - it's possible. In general case, you have to setup several dispatchers in your application.conf:

    They all are decribed in Configuration Section of spray documentation. And they all are pointing to "akka.actor.default-dispatcher" (see Dispatchers), so you can change all of them by changing this one.

    The problem here is that calling thread is not guaranteed to be your thread actually, so it will NOT help much with your tests. Just imagine if some of actors registers some handler, responding to your message:

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

    The response may be sent from another thread, so response.value may still be None in current. Actually, the response will be sent from the listening thread of underlying socket library, indepently from your test's thread. Simply saying, request may be sent in one (your) thread, but the response is received in another.

    If you really really need to block here, I would recommend you to move such code samples (like IO(Http) ? HttpRequest) out and mock them in any convinient way inside your tests. Smtng like that:

    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]
    

    By the way, you can mock IO(HTTP) with another TestActorRef to the custom actor, which will do the outside requests for you - it should require minimal code changes if you have a big project.