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?
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 thegetLastSender()
method.