Testing of cakes in Akka actors

External dependencies in Akka actors using the cake pattern

Introduction

Akka actors hide external dependencies stored in local members from the ouside world because the actor can only get instanciated through the actor system. This is very useful when you think about decoupling logic as well as distributing the actors over several nodes. The instanciation and execution responsibility stays fully in control of the actor system. But this advantage has some drawbacks when you think about unit testing an actor. Because all members and functions are hidden to the test case, there is no way to access the external dependencies nor to create mock objects for those dependencies. Therefore there are several "test safe" patterns to handle external dependencies in Akka actors. I will wrap up some possibiltities and explain a concrete pattern in detail.

Those possibilties are:

1. Providing references to external dependencies through the constructor

When instanciating an actor through the actor system it is possible to provide the construction arguments to the akka Props object.

class SomeActor(externalRef:SomeDatabase) extends Actor {
}
object SomeActor {
  def props(externalRef:SomeDatabase) = Props(classOf[SomeActor], externalRef)
}

Using this pattern allows to mock the external reference in a test case and instanciate the actor with that mock object. I don't use this pattern because the dependency management must be done from the outside. When you think that nested dependencies building the whole tree must be managed through a single point in the system, this can get complicated and messy.

2. Providing references to external dependencies through a message

With a constructed actor it is possible to provide external dependencies using an initialization message which will be sent to the instanciated actor:

object SomeActor {
  case class Initialize(externalRef: SomeDatabase)
}
class SomeActor extends Actor {
  var externalRef: SomeDatabase = _
  def receive: Receive = {
    case Initialize(ref) => externalRef = ref
  }
}

In this pattern the responsibility to build the dependency tree gets delegated to the initializer of the actor which must ensure the actor gets initialized before its first usage. There are some drawbacks in this approach. What happens if:

  • the actor is not initialized when first using it?

  • the actor gets reinitialized after using it?

There are of course other patterns to deal with this exceptional cases but I don't think that we need yet other additional patterns to handle external dependencies. That leads to a more complicated system.

3. Using a runtime dependency injection framework like Google Guice

Introducing another dependency injection framework increases the complexity of the system when we think of compile time dependency mechanism already provided by scala. Do we really need another framework for that? Well, that depends on the complexity of the whole system.

4. Cake Pattern

Scala itself provides an injection pattern assembled at compile time which ensures that all objects using the cake pattern are fully initialized with a concrete implementation. This blog post gives you a good explanation of the cake pattern. We did use that pattern in non actor based system components to manage external dependencies and keep those implementations testable. I liked it very much, but how can we use that simple and clean pattern in the Akka actor ecosystem?
The next section provides a step by step guide of how this pattern can be applied.

Cake pattern

Example setup

In our example setup we would like to use an actor to execute some database operations as an endpoint of a whole akka based message system. The TaskActor needs therefore to have an access to the TaskRepository layer. The TaskRepository provides the following database operations:

trait TaskRepository {
  def deleteByUser(userId: String): Future[Boolean]
  def insert(userId: String, task: Task): Future[Task] def getTasksByUser(userId: String): Future[Traversable[Task]]
}

The TaskActor iteself supports the folowing events:

object TaskActor {
  case object DeleteTasks
  case class AddTask(task: Task)
  case object GetTasks 
}

Using the cake pattern

We would like to use the cake pattern in Akka actors as well to provide compile time injection of external dependencies like webservices or database layers. In our example we declared the cake components containing all external references of the akka actor as follows:

trait TaskActorComponent {
  val taskRepository: TaskRepository
}

And this component we inject into our actor class

class TaskActor(userId: String) extends Actor with ActorLogging {
    self: TaskActorComponent =>

This allows us to access the generic database layer from the injected component

class TaskActor(userId: String) extends Actor with ActorLogging {
  self: TaskActorComponent =>
  import actors.TaskActor._
  def receive: Receive = {
    case DeleteTasks =>
      taskRepository.deleteByUser(userId)
    case AddTask(task) =>
      taskRepository.insert(userId, task)
  }


Wrapping up

Now we can inject the real TaskRepository and TaskActorComponent implementation by subclassing the generic one:

class DefaultTaskActor(userId: String) extends TaskActor(userId) with DefaultTaskActorComponent

The DefaultTaskActorComponent is declared as follows:

trait DefaultTaskActorComponent extends TaskActorComponent {
  val taskRepository: TaskRepository = new InMemoryTaskRepositoryImpl
}

In the props of the companion object we declare the default actor implementation bound to the real database layer:

object TaskActor {
  def props(userId: String) = Props(classOf[DefaultTaskActor], userId)
}

Testing the external dependencies

Now that we've provided the cake pattern to Akka actors we should think about mocking the external references in the test cases without the need to change the implementation of the actor. First of all we need to provide a TaskActor implementation with the possibiltity to mock and access the external dependencies from the testcase itself. That is very easy, just create a TaskActorMock and provide all external references as constructor arguments to this actor:

class TaskActorMock(userId: String, val taskRepository: TaskRepository) extends TaskActor(userId) with TaskActorComponent {
}

Pay attention to the val argument in the constructor which will map the taskRepository constructor argument to the value declared in the TaskActorComponent trait.

I previously voted against providing external dependencies through a constructor argument. Why am I now changing my mind and use those constructor argument in this TaskActorMock implementation? The explanation is rather simple: we focus here on setting up the test environment of the TaskActor. That implies that we must have the knowledge of the external dependencies of this Actor to mock them, so the dependencies are even part of the testing. And we only know one level of dependencies, those from one actor to the external systems. When we have nested dependencies, all parts of this dependency tree are handled in own test cases.

To synchronize the execution of the akka message with the test case we added an own message to the actor which sends back an ack as soon as the message got processed. This is the only change made to the actor implementation to keep it testable.

def receive: Receive = {
    case AddTask(task) =>
      taskRepository.insert(userId, task)
      sender ! Ack
}

Using this TaskActorMock and the ack message we can now instanciate the actor in two ways. Either we provide all external references when constructing the props object:

object TaskActorMock {
  def props(userId: String, taskRepository: TaskRepository) = Props(classOf[TaskActorMock], userId, taskRepository)
}

And use it in a testcase like:

class TaskActorSpec extends Specification with Mockito {
  "TaskActor insert" should {
    "invoke insert on repository" in new ActorTestScope {
      val userId = "noob"
      val probe = TestProbe()
      val taskRepository = mock[TaskRepository]
      val actorRef = system.actorOf(TaskActorMock.props(userId, taskRepository))
      val task = Task("task")
      probe.send(actorRef, AddTask(task))
      probe.expectMsg(Ack)
      there was one(taskRepository).insert(isEq(userId), isEq(task))
    }
  }
}

And the even better solution would be to mock all external dependencies in a mock of the whole component:

class TaskActorComponentMock extends TaskActorComponent with Mockito {
  val taskRepository = mock[TaskRepository]
}

And use this ComponentMock to instanciate the props object:

object TaskActorMock {
  def props(userId: String, taskActorComponent: TaskActorComponent) = Props(classOf[TaskActorMock], userId, taskActorComponent.taskRepository)
}

The test case will get simpler when the actor itself has more than one external dependencies. The test case might look like:

"TaskActor getByUser" should {
    "return tasks from repository" in new ActorTestScope {
      val userId = "noob"
      val probe = TestProbe()
      val taskActorComponent = new TaskActorComponentMock
      val actorRef = system.actorOf(TaskActorMock.props(userId, taskActorComponent))
      val tasks = Seq(Task("task1"), Task("task2"))
      taskActorComponent.taskRepository.getTasksByUser(isEq(userId)) returns Future.successful(tasks)
      probe.send(actorRef, GetTasks)
      probe.expectMsg(tasks)
    }
  }

Summary

This post demonstrates the possibilty to manage external dependencies in testable Akka actors without adding major changes to the actor implementation. The dependency injection is solved using an integrated scala feature, the cake pattern. 

The source code is available on github.

You might be interested as well in the following post about dealing with asynchronous results of external dependencies in Akka actors.