Search code examples
intellij-ideaakkafsm

Testing akka FSM with futures


I have an akka FSM actor which uses futures in states. For example:

when(StateA) {
  case Event(str: String, _) =>
    if (str == "ping") {
      Future("await-ping").pipeTo(self)(sender)
      goto(AwaitStateA)
    } else {
      stay() replying "stay-ping"
    }
}

when(AwaitStateA) {
  case Event(str: String, _) =>
  goto(StateA) replying str
}

Tests for actor above using akka testkit:

val adaptation: TestFSMRef[State, Data, _ <: Actor]

"Switch between states A" must {
  "should return `await-ping`" in {
    adaptation ! "ping"
    expectMsg("await-ping")
    adaptation.stateName should be(StateA)
  }
  "should return `stay-ping`" in {
    adaptation ! "pong"
    expectMsg("stay-ping")
    adaptation.stateName should be(StateA)
  }
}

Full code for tests you can find on github: https://github.com/azhur/fsmtest

The Problem is that tests failed randomly (sometimes they all passed). Failures appear in test "should return await-ping" -> "AwaitStateA was not equal to StateA". Please help to find where am I mistaken.

I try to run tests from command line and from IDE (Intellij IDEA). Results are the same. When I run each test separately, is hard to catch failure.


Solution

  • The future is running on the global ExecutionContext (that you have imported) and there is a race between that and the calling thread dispatcher that is used by TestFSMRef.

    I would not use TestFSMRef here. If it is important to verify the state transitions you can use the FSM transition listener instead. Something like this:

    val adaptation: ActorRef = system.actorOf(Props[FsmSwitcher1])
    
    "should return `await-ping`" in {
      val transitionListener = TestProbe()
      adaptation ! SubscribeTransitionCallBack(transitionListener.ref)
      transitionListener.expectMsg(CurrentState(adaptation, StateA))
      adaptation ! "ping"
      expectMsg("await-ping")
      transitionListener.expectMsg(Transition(adaptation, StateA, AwaitStateA))
      transitionListener.expectMsg(Transition(adaptation, AwaitStateA, StateA))
    }