erip erip - 4 days ago 6
Scala Question

How can I make my local actor more testable?

I have an actor system which roughly looks like this:

class LocalActor extends Actor {

// create the remote actor
val remoteActor = context.actorSelection("akka.tcp://RemoteSystem@127.0.0.1:2552/user/RemoteActor")

def receive = {
case foo: String => remoteActor ! s"foo = ${foo}"
case bar: Int => remoteActor ! s"bar = ${bar}"
case _ => remoteActor ! "No clue..."
}
}


I'd like to refactor it so
remoteActor
's TCP/IP isn't hardcoded. The easiest change is to pass it explicitly to the constructor:

class LocalActor(TcpIp: String) extends Actor {

val remoteActor = context.actorSelection(TcpIp)
// ...
}


But I fear this might cause problems in the case that the TCP/IP address is already in use. The option that makes the most sense to me is to pass the actor (or a reference or similar) to the constructor -- is there an idiomatic way to do this in Akka?

I'm a bit confused because the type of
remoteActor
is
akka.actor.ActorSelection
, where I might have expected it to be an
Actor
or
ActorRef
.

Luckily the remote actor interacts only with
sender
, so it is fine the way it is; but the local actor is still tricky.

If my idea above is not a good one, what's the traditional way to make this more generic and testable?

Answer

One way to achieve this is directly passing the reference to your actor class:

object LocalActor {
  def prop(remoteActor: ActorRef) = Props(new LocalActor(remoteActor))
}

class LocalActor(remoteActor: ActorRef) extends Actor {
  def receive = {
    case foo: String => remoteActor ! s"foo = ${foo}"
    case bar: Int => remoteActor ! s"bar = ${bar}"
    case _ => remoteActor ! "No clue..."
  }
}

Then wherever you are creating your LocalActor you can also create the reference to remoteActor by resolving the actorSelection:

val system = ActorSystem("yourSystem")
implicit val resolveTimeout = Timeout(5 seconds)
val remoteActor = Await.result(system.actorSelection("akka.tcp://RemoteSystem@127.0.0.1:2552/user/RemoteActor").resolveOne(), resolveTimeout.duration)
val localActor = system.actorOf(LocalActor.props(remoteActor), "LocalActor")

Then for testing you just need to inject a TestProbe:

val testProbe = TestProbe()
val testingLocalActor = system.actorOf(LocalActor.props(testProbe.ref))
val testString = "TEST"
testingLocalActor ! testString
testProbe.expectMsg(s"foo = $testString")
Comments