How to get an ActorContext from Akka testKit
In Akka Typed we need an ActorContext to create new actors.
This poses some problems if we want to create an actor inside a class that’s not an actor. We can pass around an ActorContext from a (higher level) actor. But if this is a longer-lived class, we have to keep in mind that this ActorContext is only valid during construction. So it’s generally frowned upon to pass around the ActorContext.
Because of this, Akka testKit doesn’t provide an ActorContext.
We can however spawn an Actor and take it’s context.
The class to test:
import akka.actor.typed.{ActorRef, Behavior}
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
import Theater._
import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.util.Timeout
import scala.concurrent.Future
import scala.concurrent.duration.DurationInt
class Theater(actorContext: ActorContext[_]) {
private val echoActor: ActorRef[Query] = actorContext.spawnAnonymous(echoBehavior)
private val timeout = new Timeout(5 seconds)
private val scheduler = actorContext.system.scheduler
def echo(in: In): Future[Out] =
echoActor.ask(ref => Query(in, ref))(timeout, scheduler)
}
object Theater {
type In = String
type Out = String
case class Query(input: In, replyTo: ActorRef[Out])
val echoBehavior: Behavior[Query] = Behaviors.setup { context =>
Behaviors.receiveMessage {
case Query(in, replyTo) =>
val out = in
replyTo ! out
Behaviors.same
}
}
}
The test:
import akka.NotUsed
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
import akka.actor.typed.{ActorRef, Behavior}
import org.scalatest.Assertion
import org.scalatest.wordspec.AnyWordSpecLike
import scala.concurrent.duration.DurationInt
class TheaterTest extends ScalaTestWithActorTestKit with AnyWordSpecLike {
def runWithContext[T](f: ActorContext[T] => Assertion): Assertion = {
def extractor(replyTo: ActorRef[Assertion]): Behavior[T] =
Behaviors.setup { context =>
replyTo ! f(context)
Behaviors.ignore
}
val probe = testKit.createTestProbe[Assertion]()
testKit.spawn(extractor(probe.ref))
probe.receiveMessage(1.minute)
}
"Theater" should {
"echo input back" in runWithContext[NotUsed] { context =>
val theater = new Theater(context)
whenReady(theater.echo("test")) { r =>
r shouldBe "test"
}
}
}
}
Notice that in this example class, we need the Scheduler from the ActorContext, for the ask function. So for this to work, the ActorContext needs to stay valid all the time we keep the Theater class around.