Not sure how to test behaviors in akka-typed

I’m struggling to find out how to test behaviors in akka-typed. Looking at the following example, from the documentation

  def bot(greetingCounter: Int, max: Int): Behavior[HelloWorld.Greeted] =
    Behaviors.receive { (context, message) =>
      val n = greetingCounter + 1
      println(s"Greeting ${n} for ${message.whom}")
      if (n == max) {
        Behaviors.stopped
      } else {
        message.from ! HelloWorld.Greet(message.whom, context.self)
        bot(n, max)
      }
    }
}

I want to write a test where I pass in an initial value for bot e.g. bot(1,2), send it a message and then check whether the next behavior from an execution of bot(1,2) is bot(2,2). I can’t seem to find any examples on how todo this.

Ok. I’ve found the snapshot documentation https://doc.akka.io/docs/akka/snapshot/typed/testing.html According to this synchronoous testing has limitations, which I agree with, unless I’ve misread something.

Looks like you can’t check the next behavior the actor will call
bot(n,max)
due to nothing being mentioned in the documentation (I’ll be happy if somebody corrects me, if I am wrong)

I can use synchronous testing for checking for effects (actor spawning, behavior stopping etc), messages sent to child actors and use asynchronous testing to ensure input messages generate the expected output messages given a particular state the actor is in.

Just worked out that if you add a debug statement

    Behaviors.receive { (context, message) =>
      context.log.debug("bot; {}, {}", greetingCounter, max)

Then in your tests you can do

testKit.logEntries() shouldBe Seq(CapturedLogEvent(Logging.DebugLevel, "bot; 1, 2"))

testKit.logEntries() shouldBe Seq(CapturedLogEvent(Logging.DebugLevel, "bot; 2, 2"))

to check the parameters passed into the behavior in each new iteration.

Hey Abdul,

I, personally, wouldn’t use a debug message for such purposes. Instead, you could include a “get” message in your actor’s protocol. That way you can inquire about its current state whenever that is needed. Consider the following example:

sealed trait Command
final case class Increase(value: Int) extends Command
final case class Decrease(value: Int) extends Command
final case class GetCurrent(replyTo: ActorRef[Int]) extends Command

object Counter {

  def init(value: Int = 0): Behavior[Command] = Behaviors.receiveMessage {
    case Increase(increment) => init(value + increment)
    case Decrease(decrement) => init(value - decrement)
    case GetCurrent(replyTo) =>
      replyTo ! value
      Behaviors.same
  }

}

class CounterSpec extends FlatSpec with BeforeAndAfterAll {
  private val testKit = ActorTestKit()
  override def afterAll(): Unit = testKit.shutdownTestKit()

  "Counter" should "be counting correctly" in {
    val probe = testKit.createTestProbe[Int]()
    val counter = testKit.spawn(Counter.init(100))
    counter ! Increase(100)
    counter ! Decrease(199)
    counter ! GetCurrent(probe.ref)

    probe.expectMessage(1)
  }

}

Alternatively, using your example, you can modify the Greet message to include the current count in the reply to actor sending the Greeted message.

Hope this helps.

Hi, echoing what @chmodas wrote you have a few options.

You can create a get status command to return some state of interest that is safe to do, e.g.

  1. case object GetState extends Command
  2. handle it: https://github.com/akka/akka/blob/master/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SupervisionSpec.scala#L59-L62
  3. test it: https://github.com/akka/akka/blob/master/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SupervisionSpec.scala#L441-L446

I hope that helps,
Helena

Thanks Borislav, I didn’t think about using a message to enquire about the internal state. That is probably better than creating noise in the form of extra logging for code testing.

Thank you helena, for the clarification.