I had a chance to look over the typed actors a bit more. Here is my feedback. Whether you agree or disagree, I hope you find it useful. I am still really excited about typed actors, the main reason I have been reluctant to switch to akka is that I was not willing to give up static typing.
Behavior by itself is easy to test as long as you are not doing anything with ActorContext. I particularly like the separation of the management method to deal with lifecycle signals, from the message method to deal with messages.
ActorContext can't be stubbed out because it leaks out the private classes InternalActorRef and ActorSystemImpl through its protected methods lookupRoot, guardian, and systemImpl.
ActorRef can't be stubbed out because of the self type on internal.ActorRefImpl. I have already demonstrated issue with this in detail with my FooApp and BarApp examples in this thread. I did look up the ScalaDoc for ActorRefImpl, and while I think its fine to hide the sendSystem and isLocal methods, I do think that if these methods were pushed down into a class that inherits from ActorRef, rather than using a self type, that would make it easier to stub out ActorRef.
ActorSystem has the same problem as ActorRef for the same reason, but it manifests much worse because the ActorSystem trait has so many more methods. To see why, consider writing a unit test to make sure an actor system is properly shut down once the program is finished. I want to test the code here:
It should be as easy as this:
And from looking at this test it seems like it is, but look what I had to do to get to this point:
On my dependency injection class I had to add another level of indirection
val eventActorSystem: ActorSystem[Event] = ActorSystem("state", stateful)
val eventActorSystemContract: ActorSystemContract[Event] =
new ActorSystemDelegate[Event](eventActorSystem)
I had to hide ActorSystem behind an interface
And then delegate to the real ActorSystem
That way, for testing I could start with a class where nothing is implemented:
Then use that not implemented class as a base for my stub
class ActorSystemStub extends ActorSystemNotImplemented[Event] {
var terminateInvocationCount = 0
override def terminate(): Future[Terminated] = {
terminateInvocationCount += 1
null
}
}
Which I finally use in my test
My impression is that most of these problems stem from exposing classes to the user that are too concrete. If we could push some of these implementation details like private clases and self types further down the inheritance chain, and remove the need for client code to have a compile time dependency on the implementation details, it would be much easier to stub out interactions and have fully unit-tested code.