Search code examples
scalamockingakkascalatestakka-testkit

Mock actor and its response in unit test


I have an actor that gets initiated inside the actor, i want to mock the actorB so that the message actorB ? GetDataForProcessing(value) is not sent to actorB and i can test the unit functionality of ActorExp.

My actor is:

class ActorExp(a: ARepo) extends Actor{

  lazy val actorB = context.system.actorSelection("user/ActorB")

  def receive: Receive = {

    case m @ GetData(value) =>
          sender() ! getData(value)
  }

  def getData(value:String) = {
    val res = actorB ? GetDataForProcessing(value)
    res map {
           ...
    }
  }
}

class ActorB(repoB: BRepo) extends Actor{...}

The test that I am trying to write is:

class ActorExpSpecSpec
        extends TestKit(ActorSystem("ActorExpSpecSpec"))
        with WordSpecLike
        with Matchers
        with JsonSupport
        with MockitoSugar
        with BeforeAndAfterAll
        with ImplicitSender {
  override def afterAll: Unit = TestKit.shutdownActorSystem(system)

  implicit val futureDuration: FiniteDuration = 60.seconds
  implicit val timeout: Timeout               = 10.seconds
  val mockedRepoA                       = mock[ARepo]

  val actorA: ActorRef                  = TestActorRef(ActorExp.props(mockedRepoA))

  "ActorExpSpecSpec" should {
    "be able to data" in {

      val action = (actorA ? GetData("value")).mapTo[Seq[String]]
      val result = Await.result(action, 10.seconds)
      result should equal("yes")
    } 
  }
}

Now I get ask time out exception as the actorB is not initialized as it requires certain parameters and I do not want to test actorB. Is there any way in the above stated scenario that I can mock actorB and its message actorB ? GetDataForProcessing(value)?


Solution

  • Add additional constructor argument to ActorExp that defines the actor selection path user/ActorB and provide it in your test form TestProbe().ref.path

    Here is some simplified example how to do it:

      class ParentActor(actorPath: String) extends Actor {
    
        private val selected = context.system.actorSelection(actorPath)
    
        override def receive: Receive = {
          case msg: String =>
            val replyTo = sender()
            //some irrelevant logic to test
            (selected ? msg).mapTo[String].onComplete {
              case Success(v)  => replyTo ! v
              case Failure(ex) => throw ex
            }
        }
      }
    
    

    Now, you can provide a TestProbe path into your actor like this when testing.

    val testProbe = TestProbe()
    val actor = system.actorOf(
      Props(new ParentActor(testProbe.ref.path.toStringWithoutAddress))
    )
    val reply = actor ? "Hello"
    testProbe.expectMsg("Hello")
    testProbe.reply("Hi")
    reply.map { res =>
      assert(res == "Hi")
    }