Search code examples
unit-testingakkaakka-testkit

TestKit.expectMsgClass in Akka


In the code below:

TestKit probe = new TestKit(system); // line 1
ActorRef deviceActor = system.actorOf(Device.props("group", "device")); // line 2

deviceActor.tell(new DeviceManager.RequestTrackDevice("group", "device"), probe.getRef()); // line 3
probe.expectMsgClass(DeviceManager.DeviceRegistered.class); // line 4
assertEquals(deviceActor, probe.getLastSender()); // line 5

If I comment out line 4, the test fails. Line 3 is enough to send the message to the actor. So what exactly line 4 is doing?


Solution

  • To understand what's going on, let's inspect the source code.

    Here is the definition of getLastSender(), in which p is a TestProbe:

    public ActorRef getLastSender() {
      return p.lastMessage().sender();
    }
    

    lastMessage is declared as the following in TestKit.scala:

    private[akka] var lastMessage: Message = NullMessage
    

    The lastMessage variable is mutated in one of two methods, receiveWhile and receiveOne. The expectMsgClass method calls the latter:

    def expectMsgClass[C](max: FiniteDuration, c: Class[C]): C = expectMsgClass_internal(max.dilated, c)
    
    private def expectMsgClass_internal[C](max: FiniteDuration, c: Class[C]): C = {
      val o = receiveOne(max)
      // ...
    }
    

    Basically, if you don't call one of the TestKit's built-in assertions (e.g., one of the expectMsg* methods) in your test, then lastMessage will remain unchanged as a NullMessage. If lastMessage is a NullMessage, then calling lastMessage.sender will result in an exception, and assertEquals(deviceActor, probe.getLastSender()); in your test will fail.

    On the other hand, if you do call a built-in assertion such as expectMsgClass, then lastMessage will be set appropriately, and the sender of lastMessage will resolve correctly.

    In short, calling getLastSender() assumes the use of a TestKit assertion. This is implied in the documentation (emphasis mine):

    The probe stores the sender of the last dequeued message (i.e. after its expectMsg* reception), which may be retrieved using the getLastSender() method.