Akka FSM and TestKit putting shared tests into Trait makes sender be deadLetters

280 views Asked by At

I am trying to factor some common tests of some common Akka FSM code into a trait but the sender ref is becoming deadLetters. Using this FSM code:

import akka.actor.FSM
import akka.testkit.TestFSMRef
import akka.testkit.TestKit
import akka.actor.ActorSystem
import org.scalatest.Matchers
import org.scalatest.WordSpecLike
import akka.testkit.ImplicitSender

sealed trait MyState
case object StateA extends MyState
case object StateB extends MyState

case class MyData(ignored: Option[Int] = None)

class MyFSM extends FSM[MyState, MyData] {
  startWith(StateA, MyData())

  val common: StateFunction = {
    case Event(_, _) =>
      System.err.println(s"sender is $sender")
      sender ! s"hello $stateName"
      stay
  }

  when(StateA)(common)

  when(StateB)(common)
}

Then I can test the common code to the two states in two specs with duplicated test code using:

class StateASpec extends TestKit(ActorSystem()) with WordSpecLike with Matchers with ImplicitSender {

  def testCommonBehaviour(testState: MyState) {
    val fsm = TestFSMRef(new MyFSM())
    fsm.setState(testState, MyData())
    fsm ! "hello fsm"
    expectMsg("hello StateA")
  }

  "StateA" should {
    "respond to common test case" in {
      testCommonBehaviour(StateA)
    }
  }
}

class StateBSpec extends TestKit(ActorSystem()) with WordSpecLike with Matchers with ImplicitSender {

  def testCommonBehaviour(testState: MyState) {
    val fsm = TestFSMRef(new MyFSM())
    fsm.setState(testState, MyData())
    fsm ! "hello fsm"
    expectMsg("hello StateB")
  }

  "StateB" should {
    "respond to common test case" in {
      testCommonBehaviour(StateB)
    }
  }
}

when I try to reactor the common test logic out into a trait with:

trait TraitOfTests {
  self: TestKit with ImplicitSender =>

  def testCommonBehaviour(testState: MyState) {
    val fsm = TestFSMRef(new MyFSM())
    fsm.setState(testState, MyData())
    fsm ! "hello fsm"
    expectMsg("hello $testState")
  }
}

class StateASpec extends TestKit(ActorSystem()) with WordSpecLike with Matchers with ImplicitSender  with TraitOfTests {

  "StateA" should {
    "respond to common test case" in {
      testCommonBehaviour(StateA)
    }
  }
}

class StateBSpec extends TestKit(ActorSystem()) with WordSpecLike with Matchers with ImplicitSender  with TraitOfTests {

  "StateB" should {
    "respond to common test case" in {
      testCommonBehaviour(StateB)
    }
  }
}

The responses go to the deadLetters actor:

sender is Actor[akka://default/deadLetters] [INFO] [11/30/2014 16:15:49.812] [default-akka.actor.default-dispatcher-2] [akka://default/deadLetters] Message [java.lang.String] from TestActor[akka://default/user/$$b] to Actor[akka://default/deadLetters] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.

How can I fix up the implicit sender to ensure that when the FSM actor responds to the call of the test make from within the trait it gets back to the testActor so that the expectMsg works?

1

There are 1 answers

0
cmbaxter On BEST ANSWER

One way to fix this problem is a slight re-design to your common trait like so:

trait TraitOfTests {
  self: TestKit =>

  def testCommonBehaviour(testState: MyState)(implicit sender:ActorRef) {
    val fsm = TestFSMRef(new MyFSM())
    fsm.setState(testState, MyData())
    fsm ! "hello fsm"
    expectMsg("hello $testState")
  }
}

If you define the implicit sender on testCommonBehavior then you can be assured that it will properly pick it up from your tests that make it available via ImplicitSender