Multiple groups in my department have started using Spray to develop REST based web services and are all running into a similar problem and there really haven't been great solutions to come out of it so far.
Suppose you had the following:
FooService extends Actor { ??? }
and then elsewhere:
path("SomePath") {
id =>
get {
requestContext =>
// I apologize for the janky Props usage here, just an example
val fooService = actorRefFactory.actorOf(Props(new FooService(requestContext)))
queryService ! SomeMessage(id)
}
}
In other words, every end point has a corresponding actor, and inside of a route an actor of that type will be spun up w/ the request context, a message will be passed to it and that actor will handle the HttpResponse & stop.
I've always had simple enough route trees that I've unit tested just the Actors themselves and let the route testing be handled by integration tests, but I've been overruled here. So the problem is that for unit tests people want to be able to replace FooService with a MockFooService
Is there a standard way of handling this situation?
I would go with a cake pattern where you can mix-in implementation at the last moment:
trait MyService extends HttpService with FooService {
val route =
path("SomePath") { id =>
get { requestContext =>
val fooService = actorRefFactory.actorOf(fooProps(requestContext))
queryService ! SomeMessage(id)
}
}
}
trait FooService {
def fooProps(requestContext: RequestContext): Props
}
trait TestFooService extends FooService {
def fooProps(requestContext: RequestContext) =
Props(new TestFooService(requestContext))
}
trait ProdFooService extends FooService {
def fooProps(requestContext: RequestContext) =
Props(new FooService(requestContext))
}
trait MyTestService extends MyService with TestFooService
trait MyProdService extends MyService with ProdFooService
I wrote it in a text editor so I'm not sure if it compiles.
If you want to do testing without actors at all you can extract these two lines:
val fooService = actorRefFactory.actorOf(fooProps(requestContext))
queryService ! SomeMessage(id)
into some method and hide an actor behind that. For instance:
def processRequest[T](msg: T): Unit = {
// those 2 lines, maybe pass other args here too like context
}
This method can be overridden in the same cake pattern way and for tests you can even avoid using actors at all.