I'm trying to obtain internal state of an actor in my unit test, but by some reason the old state persists.
My actor should be adding/removing/listing self-registering actor services:
class DirectoryServiceActor extends Actor {
var servicesMap: Map[String, List[ActorRef]] = Map.empty[String, List[ActorRef]]
def receive = {
case AddService(serviceType) ⇒
servicesMap = servicesMap + (serviceType -> (sender :: servicesMap.getOrElse(serviceType, List.empty[ActorRef])))
sender ! Ack
case RemoveService ⇒
val oldMap = servicesMap
servicesMap = servicesMap.mapValues(list ⇒ (if (list.contains(sender)) list.diff(List(sender)) else list).toList)
println(servicesMap)
if (servicesMap.equals(oldMap)) {
sender ! Nack
} else {
sender ! Ack
}
case ListServices ⇒
sender ! services
}
def services: Map[String, List[ActorRef]] = this.servicesMap
}
And my test is
"Remove existing service successfully" in {
implicit val timeout = 10 millis
val probe = new TestProbe(system)
val directoryService = TestActorRef[DirectoryServiceActor]
val actor = directoryService.underlyingActor
directoryService.tell(AddService("test"), probe.ref)
probe.expectMsg(timeout, Ack)
directoryService.tell(RemoveService, probe.ref)
probe.expectMsg(timeout, Ack)
println("TEST: " + actor.services)
actor.services("test") should not contain (probe.ref)
}
Judging by failed test and console output it seems that actor.underlyingActor.services returns the old value:
Map(test -> List())
TEST: Map(test -> List(Actor[akka://myApp/system/testActor3#-2080677614]))
Even though inside of the actor, the variable has already been set to a new value. What have I missed?
Update: Seems not to be related to Akka, actually, but can be worked around using futures in the test:
"Remove existing service successfully" in {
implicit val timeout = Timeout(100 millis)
val directoryService = TestActorRef[DirectoryServiceActor]
val addResponseFuture = directoryService ? AddService(self, "test")
addResponseFuture.value.get should be(Success(Ack(self)))
val removeResponseFuture = directoryService ? RemoveService(self)
removeResponseFuture.value.get should be(Success(Ack(self)))
val listResponseFuture = directoryService ? ListServices
listResponseFuture.value.get should be(Success(Map("test" -> List())))
val actor = directoryService.underlyingActor
actor.services("test") should not contain (self)
}
I suppose that it is happening due to mapValue not actually creating a new map: Scala: Why mapValues produces a view and is there any stable alternatives?