I am wondering what the canonical way is of testing whether an actor has stopped in akka. Here is an example of how I am currently doing; I'm worried I'm over complicating it.
import akka.actor.{Terminated, Actor, Props, ActorSystem}
import akka.testkit.TestProbe
class MyActor extends Actor {
import MyActor._
override def receive: Receive = {
case Stop => context.stop(self)
}
}
object MyActor {
def props = Props(new MyActor)
case object Stop
}
object MyActorSpec {
val system = ActorSystem()
val myActor = system.actorOf(MyActor.props)
val testProbe = TestProbe()
case object MyActorStopped
val watcher = system.actorOf(Props(new Actor {
context.watch(myActor)
override def receive: Actor.Receive = {
case Terminated(`myActor`) => testProbe.ref ! MyActorStopped
}
}))
myActor ! MyActor.Stop
testProbe.expectMsg(MyActorStopped)
}
You can get rid of the separate watcher actor, and just watch the target actor directly from the testProbe actor:
val testProbe = TestProbe()
testProbe watch myActor
myActor ! MyActor.Stop
testProbe.expectTerminated(myActor)
See here for the relevant section of the Akka testing docs.
Related
As the doc of akka explains, you should be able to gain the pipeTo method on [[scala.concurrent.Future]] this way:
import akka.pattern.pipe
val future = ...
future pipeTo sender()
Unfortunately, I can't do that, i'm getting an error "can't resolve symbol pipeTo" in my IDE.
As a workaround, I had to use the syntax this way
pipe(future) pipeTo sender()
But it still disturb me to not figure out why (i'm quite newby in scala BTW). Thanks a lot to help understand this puzzle.
scala 2.12.2
akka 2.5.3
You need to have an implicit ExecutionContext in scope, here is an example:
import akka.actor.{Actor, ActorSystem, Props}
import akka.pattern.pipe
import scala.concurrent.Future
// Get the implicit ExecutionContext from this import
import scala.concurrent.ExecutionContext.Implicits.global
object Hello extends App {
// Creating a simple actor
class MyActor extends Actor {
override def receive: Receive = {
case x => println(s"Received message: ${x.toString}")
}
}
// Create actor system
val system = ActorSystem("example")
val ref = system.actorOf(Props[MyActor], "actor")
// Create the future to pipe
val future: Future[Int] = Future(100)
// Test
future pipeTo ref
}
Console:
sbt run
[info] <stuff here>
[info] Running example.Hello
Received message: 100
The reason you have to do that is because pipeTo is a instance function on a PipeableFuture, and your regular Future has to be "enhanced" to a PipeableFuture. Here is the constructor for PipeableFuture, note the implicit executionContext: ExecutionContext parameter:
final class PipeableFuture[T](val future: Future[T])(implicit executionContext: ExecutionContext)
The full class is here, where you can see the pipeTo function:
final class PipeableFuture[T](val future: Future[T])(implicit executionContext: ExecutionContext) {
def pipeTo(recipient: ActorRef)(implicit sender: ActorRef = Actor.noSender): Future[T] = {
future andThen {
case Success(r) ⇒ recipient ! r
case Failure(f) ⇒ recipient ! Status.Failure(f)
}
}
def pipeToSelection(recipient: ActorSelection)(implicit sender: ActorRef = Actor.noSender): Future[T] = {
future andThen {
case Success(r) ⇒ recipient ! r
case Failure(f) ⇒ recipient ! Status.Failure(f)
}
}
def to(recipient: ActorRef): PipeableFuture[T] = to(recipient, Actor.noSender)
def to(recipient: ActorRef, sender: ActorRef): PipeableFuture[T] = {
pipeTo(recipient)(sender)
this
}
def to(recipient: ActorSelection): PipeableFuture[T] = to(recipient, Actor.noSender)
def to(recipient: ActorSelection, sender: ActorRef): PipeableFuture[T] = {
pipeToSelection(recipient)(sender)
this
}
}
Since pipe(future) was not an instance function on a Future, it works in your example.
I am trying to test an actor, that depends on another actor. In the following test ActorA sends MessageA to ActorB and expects a MessageB in return.
Since this is a Unit test for ActorB, I am mocking ActorA. My problem now is, that i want to be sure that ActorA gets MessageA, but when i call the expectMsg (see the commented line) the whole autopilot does not seem to work anymore.
Am I doing something wrong here?
class MyTest(_system: ActorSystem) extends TestKit(_system)
with WordSpecLike
with BeforeAndAfterAll {
object Start
object MessageA
object MessageB
def this() = this(ActorSystem("TestSpec"))
override def afterAll() {
system.shutdown()
}
// will later be mocked
class ActorA extends Actor {
def receive = {
case MessageA => sender() ! MessageB
}
}
class ActorB(actorA: ActorRef) extends Actor {
def receive = {
case Start => actorA ! MessageA
case MessageB => println("Got MessageB")
}
}
"" should {
"work" in {
val actorA = TestProbe()
actorA.setAutoPilot(new AutoPilot {
override def run(sender: ActorRef, msg: Any): AutoPilot = {
msg match {
case MessageA => sender ! MessageB
}
TestActor.KeepRunning
}
})
// this will break the test
//actorA.expectMsg(MessageA)
val actor = system.actorOf(Props(new ActorB(actorA.ref)))
actor ! Start
}
}
}
You should expect MessageA after sending start to your ActorB.
val actor = system.actorOf(Props(new ActorB(actorA.ref)))
actor ! Start
// this will not break the test
actorA.expectMsg(MessageA)
I have a Controller class which controls the requests sent to a Akka actor which is injected in to the controller.
Controller's code:
class Controller(actor: ActorRef) {
def control(msg: String): Future[String] = {
actor.ask(msg)(Timeout(2 seconds)).mapTo[String]
}
}
And my Actor's code is:
class ActorA extends Actor {
override def receive: Receive = {
case msg: String => sender ! msg
case msg: Int => sender ! msg.toString
case _ => "Invalid command!"
}
Now I need to mock ActorA's behaviour to unit test Controller. Is there a way to do so via Akka TestKit ?
Use a TestProbe. From the testing documentation:
val probe = TestProbe()
val future = probe.ref ? "hello"
probe.expectMsg(0 millis, "hello")
probe.reply("world")
assert(future.isCompleted && future.value == Some(Success("world")))
I am fairly new to akka and not sure about approaching this problem.
I have a Monitor actor that spawns 2 other actors as DiskMonitor and MemoryMonitor
DiskMonitor checks the diskUsage and report DiskReport message
MemoryMonitor checks the diskUsage and report MemoryReport message
I want to combine the DiskReport and MemoryReport into Report Object and send to to remote machine via an API.
Questions
Since DiskMonitor and MemoryMonitor both are actors they send the response back to caller once they are done with their work
Monitor
diskMonitor ! DiskMonitorRequest
memoryMonitor ! MemoryMonitorRequest
How would Monitor know that it has all the results that it needs so that it can create Report object and send it via API?
Are actors good way to approach this problem?
I also read about future but I don't understand them well and not sure if they would be helpful in this problem context
This is a simple way of doing these things. Other options can be through using context.become or the trait FSM.
class Monitor extends Actor {
// Somewhere do this:
diskMonitor ! DiskMonitorRequest
memoryMonitor ! MemoryMonitorRequest
var diskMonitorResult = Option.empty[DiskMonitor]
var memoryMonitorResult = Option.empty[MemoryMonitor]
def recieve = {
case d: DiskMonitor =>
diskMonitorResult = Some(d)
checkIfCompleted()
case m: MemoryMonitor =>
memoryMonitorResult = Some(m)
checkIfCompleted()
}
def checkIfCompleted = {
(diskMonitorResult, memoryMonitorResult) match {
case (Some(diskMonitor), Some(memoryMonitor)) =>
// send to external API
externalApi ! Report(diskMonitor, memoryMonitor)
// Possibly stop this actor
case _ => // do nothing
}
}
}
You can use ask pattern and combine futures..
import akka.actor._
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.ExecutionContextExecutor
import scala.concurrent.duration._
import scala.language.postfixOps
class Monitor extends Actor with ActorLogging {
implicit val timeout: Timeout = Timeout(5 seconds)
implicit val ec: ExecutionContextExecutor = context.dispatcher
val diskM: ActorRef = context.system.actorOf(Props[DiskMonitor])
val memoryM: ActorRef = context.system.actorOf(Props[MemoryMonitor])
val remoteM: ActorRef = context.system.actorOf(Props[RemoteMachine])
override def preStart: Unit = {
log.info("Monitor Created..")
self ! GenerateReport
}
override def receive: Receive = {
case GenerateReport =>
(diskM ? MakeDiskReport).mapTo[DiskReport].zip((memoryM ? MakeMemoryReport)
.foreach { case (diskR, memR) =>
remoteM ! Report(memR.report, diskR.report)
}
}
}
class DiskMonitor extends Actor with ActorLogging {
override def receive: Receive = {
case MakeDiskReport =>
log.info("Creating DiskReport..")
val client = sender
client ! DiskReport("DiskReport")
}
}
class MemoryMonitor extends Actor with ActorLogging {
override def receive: Receive = {
case MakeMemoryReport =>
log.info("Creating MemoryReport..")
val client = sender
client ! MemoryReport("MemoryReport")
}
}
class RemoteMachine extends Actor with ActorLogging {
override def receive: Receive = {
case Report(memr, diskr) =>
log.info(s"Final Report.. $memr, $diskr")
}
}
object Main extends App {
val sys = ActorSystem()
sys.actorOf(Props[Monitor])
}
sealed trait Message
case object GenerateReport extends Message
case object MakeDiskReport extends Message
case object MakeMemoryReport extends Message
case class DiskReport(report: String) extends Message
case class MemoryReport(report: String) extends Message
case class Report(memReport: String, diskReport: String) extends Message
Below class is causing an error at line new HelloWorld :
Exception in thread "main" akka.actor.ActorInitializationException: You cannot create an instance of [HelloWorld] explicitly using the constructor (new). You have to use one of the 'actorOf' factory methods to create a new actor. See the documentation.
at akka.actor.ActorInitializationException$.apply(Actor.scala:219)
at akka.actor.Actor$class.$init$(Actor.scala:436)
at HelloWorld.<init>(HelloWorld.scala:4)
at Driver$.main(HelloWorld.scala:38)
at Driver.main(HelloWorld.scala)
So I try : val hw = actorOf(new HelloWorld)
But this causes a compiler error :
not found: value actorOf
How should HelloWorld below be implemented ?
Reading other Scala docs an act method is requried to be defined within the class that extends Actor and then invoke the start method on this class, is there a reason for using actorOf instead of defining an act method ?
Below class is taken from Scala akka docs http://doc.akka.io/docs/akka/2.2.0/scala.html :
import akka.actor.Actor
import akka.actor.Actor._
import akka.actor.Props
class HelloWorld extends Actor {
override def preStart(): Unit = {
// create the greeter actor
val greeter = context.actorOf(Props[Greeter], "greeter")
// tell it to perform the greeting
greeter ! Greeter.Greet
}
def receive = {
// when the greeter is done, stop this actor and with it the application
case Greeter.Done => context.stop(self)
}
object Greeter {
case object Greet
case object Done
}
class Greeter extends Actor {
def receive = {
case Greeter.Greet =>
println("Hello World!")
sender ! Greeter.Done
}
}
}
object Driver {
def main(args: Array[String]) {
new HelloWorld
}
}
You need to edit your main as shown below. Secondly, in line-5, you need to change it to context.actorOf(Props(new Greeter)). This is because your Greeter does not have apply function defined, hence you need to manually create Greeter object yourself.
Working code below:
import akka.actor.ActorSystem
class HelloWorld extends Actor {
override def preStart(): Unit = {
// create the greeter actor
val greeter = context.actorOf(Props(new Greeter), "greeter")//line 5
// tell it to perform the greeting
greeter ! Greeter.Greet
}
def receive = {
// when the greeter is done, stop this actor and with it the application
case Greeter.Done => context.stop(self)
}
object Greeter {
case object Greet
case object Done
}
class Greeter extends Actor {
def receive = {
case Greeter.Greet =>
println("Hello World!")
sender ! Greeter.Done
}
}
}
object Driver {
def main(args: Array[String]) {
val system = ActorSystem("Main")
val ac = system.actorOf(Props[HelloWorld])
}
}
If you want to use your main class do the following:
import akka.actor.{ActorSystem, Props}
object Driver extends App {
val system = ActorSystem("System")
val hw = system.actorOf(Props[HelloWorld], name = "hw")
}
Which will create a new actor system and then create the HelloWorld actor using that actor system.
You can also follow the akka instructions:
set Akka.Main as the main class and give the program "com.example.HelloWorld" as argument.
val greeter = context.actorOf(Props(new Greeter), "greeter")//line 5
I don't think you need to have a new keyword there for Greeter. I believe the Props does that for you already. If anything having a new should be a