Reputation: 443
I expanded the example from http://doc.akka.io/docs/akka/snapshot/scala/testing.html#Using_Multiple_Probe_Actors.
import akka.actor._
import akka.testkit.{TestProbe, TestKit}
import org.scalatest.{Suites, BeforeAndAfter, BeforeAndAfterAll, FlatSpecLike}
import scala.concurrent.duration._
class TestProbesTestSuites extends Suites(new TestProbesTest)
class TestProbesTest extends TestKit(ActorSystem("TestProbesTestSystem")) with FlatSpecLike with BeforeAndAfterAll with BeforeAndAfter {
override def afterAll: Unit = {
TestKit.shutdownActorSystem(system)
}
"A TestProbeTest" should "test TestProbes" in {
val actorRef = system.actorOf(Props[TestActor], "TestActor")
val tester1 = TestProbe()
val tester2 = TestProbe()
Thread.sleep(500.milliseconds.toMillis)
actorRef ! (tester1.ref, tester2.ref)
// When you comment the next line the test fails
tester1.expectMsg(500.milliseconds, "Hello")
tester2.expectMsg(500.milliseconds, "Hello")
// Alternative test
// Thread.sleep(500.milliseconds.toMillis)
// tester1.expectMsg(0.milliseconds, "Hello")
// tester2.expectMsg(0.milliseconds, "Hello")
()
}
}
class TestActor extends Actor with ActorLogging {
override def receive: Receive = {
case (actorRef1: ActorRef, actorRef2: ActorRef) => {
// When you change the schedule time in the next line to 100.milliseconds the test fails
context.system.scheduler.scheduleOnce(400.milliseconds, actorRef1, "Hello")(context.system.dispatcher)
context.system.scheduler.scheduleOnce(800.milliseconds, actorRef2, "Hello")(context.system.dispatcher)
}
case x => log.warning(x.toString)
}
}
I do not think that this a correct or good usage for a test.
When I remove the tester1.expectMsg(500.milliseconds, "Hello")
the test fails,
so testing of tester2 dependes on testing tester1. In my opinion this bad.
Also changing line context.system.scheduler.scheduleOnce(400.milliseconds, actorRef1, "Hello")(context.system.dispatcher)
to a delay of 100 milliseconds let the test fail. So testing message 2 depends on sending message 1. In my opinion this is bad, too.
To solve this I would add a Thread.sleep after sending the message and change the wait time for both #expectMsg to 0 milliseconds. Thread.sleep does also not look good for me in tests, but I think it is a must in actor testing. Is this the right way?
I thought TestProbe is made for testing multiple actors. But for me the wait time parameter of #expectMsg is quite useless, when testing multiple actors.
Any remarks are welcome.
Upvotes: 4
Views: 8211
Reputation: 35463
You could try a within
block instead like so:
within(800.milliseconds, 900.milliseconds){
tester1.expectMsg("Hello")
tester2.expectMsg("Hello")
}
That way if you comment out the assertion to tester1
the test still passes.
Upvotes: 3
Reputation: 879
The test probe works synchronously. The expectMsg
method is blocking. And it's good, because you want to run tests in one thread and have control over it. There are no race conditions this way.
The timeout while expecting messages is measured from the time the line is read, which means that the 500 millis in tester2.expectMsg(500.milliseconds, "Hello")
will start counting down only after tester1
receives his message.
If tester1
doesn't get the message, you want to fail the test immediately, you don't need to continue to tester2
.
On the other hand, scheduler is done asynchronously, in multiple threads, and the countdown starts right away because there is no blocking. So the scheduled 400 millis and 800 millis will start counting down (nearly) at the same time.
So changing the 400 millis to 100 millis means that the second message will still be sent approximately 800 millis from start, but tester1
will be expecting it 100 + 500 = 600 millis from start. That's why it fails.
Upvotes: 3