How to use Akka-TestKit TestProbe correctly?

hami picture hami · Aug 4, 2015 · Viewed 7.2k times · Source

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.

Answer

cmbaxter picture cmbaxter · Aug 4, 2015

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.