Skip to Content
Course content

96: Testing Akka Actors

Click on the "Edit" button in the top corner of the screen to edit your slide content.

How do I actually "see" what an actor is sending since they don't return values?

This is the first hurdle everyone hits with Akka. In a normal class, you call a method and check the return value. With actors, you send a message into a void and hope for the best. To test this, we use a TestProbe. Think of a probe as a "fake" actor that you control. You can tell it to expect a specific message, and if that message doesn't show up within a certain timeframe, the test fails.

Let's say we have a PaymentProcessor actor that sends a PaymentConfirmed message to a tracking actor. You don't want to spin up a whole tracking system just for a unit test. Instead, you do this:

import akka.testkit.{TestKit, TestProbe}
import akka.actor.ActorSystem
import org.scalatest.wordspec.AnyWordSpecLike

class PaymentProcessorSpec extends TestKit(ActorSystem("PaymentSystem")) 
  with AnyWordSpecLike {

  "A PaymentProcessor" should {
    "send a confirmation message after processing" in {
      val probe = TestProbe()
      // We pass the probe's ref instead of a real ActorRef
      val processor = system.actorOf(Props(new PaymentProcessor(probe.ref)))

      processor ! ProcessPayment(amount = 100)
      
      // This blocks until the message arrives or it times out
      probe.expectMsg(PaymentConfirmed(100))
    }
  }
}


Do I have to use Thread.sleep to wait for the actor to finish?

Please, whatever you do, don't use Thread.sleep(). I've seen far too many CI pipelines slowed to a crawl—or worse, made "flaky"—because someone added a 500ms sleep to a test. Actors are asynchronous, but TestProbe handles the waiting for you.

Methods like expectMsg or expectMsgPF (the partial function version) are designed to poll the actor's mailbox. They wait just long enough for the message to arrive and then immediately proceed. If the message takes 2ms, the test takes 2ms. If it takes 2 seconds, it waits 2 seconds. It's much more efficient and reliable than guessing how long a thread needs to sleep.

How do I test an actor that depends on other actors?

The secret here is dependency injection. Don't let your actor create its own dependencies using context.actorOf inside the constructor if you want to test it easily. Instead, pass the ActorRefs of those dependencies in as arguments. This allows you to swap a real actor for a TestProbe during testing.

Imagine an OrderManager that needs to talk to an InventoryActor. If you hardcode the InventoryActor, you're testing two actors at once, which is an integration test, not a unit test. Here is how I usually structure it:

class OrderManager(inventoryActor: ActorRef) extends Actor {
  def receive = {
    case PlaceOrder(item) => 
      inventoryActor ! CheckStock(item)
  }
}

// In your test:
val inventoryProbe = TestProbe()
val orderManager = system.actorOf(Props(new OrderManager(inventoryProbe.ref)))

orderManager ! PlaceOrder("Scala Book")
inventoryProbe.expectMsg(CheckStock("Scala Book"))

By doing this, you've isolated the OrderManager. You aren't testing if the inventory system works—you're testing that the OrderManager knows how to ask the inventory system for stock. That separation makes your tests run faster and makes it obvious where a bug actually lives.




📋 Practical Task

Test the NotificationDispatcher Actor

You are tasked with testing a NotificationDispatcher actor. This actor is responsible for sending a notification to two different services: an Email service and an SMS service. Whenever the dispatcher receives a SendAlert(message: String) command, it should send that exact message to both the email actor and the SMS actor.

Your Goal: Write a ScalaTest specification using Akka TestKit that verifies the NotificationDispatcher sends the correct message to both dependencies.

Requirements:

  • Create two TestProbe instances (one for email, one for SMS).
  • Initialize the NotificationDispatcher by passing in the ref of both probes.
  • Send a SendAlert("System Overload!") message to the dispatcher.
  • Use expectMsg on both probes to ensure they both received the "System Overload!" string.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.