I have a Akka actor as follows; it receives a message and returns a HTTP response.
I am having trouble testing the interaction with Dispatch Http, it is a nice library but seems difficult to test.
class Service(serviceUrl:String) extends Actor with ActorLogging {
implicit val ec = context .dispatcher
override def receive: Receive = {
case Get(ids) => request(ids)
}
private def request(ids:Seq[Int]):Unit = {
val requestUrl = buildRequestUrl(ids)
val request = url(requestUrl).GET
Http(request) pipeTo sender()
}
}
One way would be to do something like this with your actor:
case class Get(ids:Seq[Int])
class Service(serviceUrl:String) extends Actor with ActorLogging {
implicit val ec = context .dispatcher
def receive: Receive = {
case Get(ids) => request(ids)
}
def request(ids:Seq[Int]):Unit = {
val requestUrl = buildRequestUrl(ids)
val request = url(requestUrl).GET
executeRequest(request) pipeTo sender()
}
def executeRequest(req:Req) = Http(req)
def buildRequestUrl(ids:Seq[Int]):String = s"http://someurl.com/?ids=${ids.mkString(",")}"
}
Here, I'm providing a method, executeRequest that all it does it execute the http request and return the result. This method will be overridden in my test like so:
class ServiceTest extends TestKit(ActorSystem("test")) with SpecificationLike with Mockito with ImplicitSender{
trait scoping extends Scope{
def mockResult:Response
var url:String = ""
val testRef = TestActorRef(new Service(""){
override def executeRequest(req:Req) = {
url = req.toRequest.getUrl()
Future.successful(mockResult)
}
})
}
"A request to service " should{
"execute the request and return the response" in new scoping{
val mockedResp = mock[Response]
def mockResult = mockedResp
testRef ! Get(Seq(1,2,3))
expectMsg(mockedResp)
url ==== s"http://someurl.com/?ids=${URLEncoder.encode("1,2,3")}"
}
}
It's a bit crude but can be effective.
Related
I have an actor of the form:
class TestActor(repo: Repo) extends Actor {
implicit val executionContext: ExecutionContext = context.dispatcher
def receive: Receive = {
...
}
def testMethod(stringSeq: Seq[String]): String =
...
}
}
I want to test the method testMethod only.
I am trying to write the test case as:
class TestActorSpec
extends TestKit(ActorSystem("TestActorSpec"))
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 mockedRepo = mock[Repo]
val testActorRef: ActorRef = system.actorOf(
TestActor.props(mockedRepo)
)
"TestActorSpec" should {
"be able to get data " in {
}
}
}
How can i access the method testMethod from testActorRef ?
I also tried:
TestActorRef[TestActor].underlyingActor.testMethod(Seq("data"))
It does not work for me.
Actors should be tested via messages you send to them. But you still can test methods in isolation if the method is not accessing any actor specific values, like context.
You can move testMethod into companion object and test it from there.
class TestActor(repo: Repo) extends Actor {
implicit val executionContext: ExecutionContext = context.dispatcher
def receive: Receive = {
// call TestActor.testMethod
}
}
object TestActor {
def testMethod(stringSeq: Seq[String]): String = ???
}
I have a actor like this:
class MyActor[T] extends Actor {
def receive = {
case Option1(t: T) =>
doWork(t) onComplete .....
case Option2 =>
}
def doWork(T): Future[T]{
}
}
Then I have an actor that inherits from the above:
class OtherActor extends MyActor {
val m = mutable.Map.empty[Int, User]
override def doWork(..) = {
//
}
}
Now in my OtherActor actor I want to add some methods to the receive method, how can I do this?
You can define the additional behavior in a Receive block inside OuterActor and chain that behavior to its parent's behavior with orElse:
class OtherActor extends MyActor {
val m = mutable.Map.empty[Int, User]
override def doWork(...) = ???
val otherBehavior: Receive = {
case ...
}
override def receive = otherBehavior.orElse(super.receive)
}
This is possible because Receive is just a type alias for PartialFunction[Any, Unit]. More information on composing actor behaviors is found here.
As a side note, you should prefer var m = immutable.Map.empty[Int, User] instead of val m = mutable.Map.empty[Int, User] in order to help avoid exposing the actor's state, as described in this answer.
class HomeController #Inject (implicit actorSystem:ActorSystem, materializer :Materializer) extends Controller
{
case class sendMsg(msg:JsValue)
class MYWSACTOR(out:ActorRef,mainActor:ActorRef) extends Actor
{
def receive =
{
case msg : JsValue =>
mainActor ! sendMsg(msg)
case sendMsg(msg) =>
{
/* validation part */
out ! msg
}
}
}
}
val mainActor = actorSystem.actorOf(Props[MYWSACTOR],"mainActor")
def object MYWSACTOR
{
def props(out:ActorRef) = Props(new MYWSACTOR(out,mainActor))
}
def socket = WebSocket.accept[JsValue,JsValue]
{
request =>
ActorFlow.actorRef(out => MYWSACTOR.props(out))
}
I am new to Akka and Scala . I am trying to make a chat application using Akka in Scala and Play framework 2.5.3 .I used the above code from the official documentation. I just wanted to create another actor (mainActor in above code) so that message from the client is validated first and then be sent back. But the problem is mainActor is unable to send the msg to another class case sendMsg. Also , if i try to create the actor at any different point, it gives me compilation error Unexpected Exception: Provisional Exception
Here is hopefully what you probably wanted:
case class SendMsg(msg: JsValue)
case class ReceivedMessage(wsActor: ActorRef, msg: JsValue)
class MyWsActor(out: ActorRef, mainActor:ActorRef) extends Actor {
def receive = {
case msg : JsValue =>
mainActor ! ReceivedMessage(self, msg)
case SendMsg(msg) =>
out ! msg
}
}
object MyWsActor {
def props(out: ActorRef, mainActor: ActorRef) = Props(new MyWsActor(out, mainActor))
//or if you want to instantiate your Props with reflection
def props2(out: ActorRef, mainActor: ActorRef) = Props(classOf[MyWsActor], out, mainActor)
}
class MainActor() extends Actor {
def receive = {
case ReceivedMessage(wsActor, msg) => {
/*
Do sth with msg
*/
val result = msg
wsActor ! SendMsg(result) //here sender is your MyWsActor
}
}
}
class HomeController #Inject() (
actorSystem: ActorSystem,
materializer: Materializer
) extends Controller {
val mainActor = actorSystem.actorOf(Props[MainActor], "mainActor")
def socket = WebSocket.accept[JsValue,JsValue] {
request =>
ActorFlow.actorRef(out => MyWsActor.props(out, mainActor))
}
}
The key is that you can send, with actual message, also ActorRef (or by default you can get a sender ActorRef calling sender()) and then you can replay to this ActorRef.
I am newbie to scala and can't figure out sending private message to client using websocket.
Here is my controller:
object Client extends Controller {
def socket(uuid: String) = WebSocket.acceptWithActor[String, String] { request =>
out => ClientWebSocket.props(uuid)
}
// Test Method to send message to websocket connected client
def sendMessage(guid: String) = Action { implicit request =>
val system = ActorSystem("default")
val out = system.actorOf(Props(classOf[ClientWebSocket], guid))
out ! SendUpdate("Message Recieved")
Ok
}
}
Here is my actor class:
object ClientWebSocket {
def props(uuid: String) = Props(new ClientWebSocket(uuid))
case class SendUpdate(msg:String)
}
class ClientWebSocket(uuid: String) extends Actor {
import ClientWebSocket._
def receive = {
case SendUpdate(msg:String) =>
sender ! "Message is " + msg
}
}
When I call sendMessage with uuid of client, I am getting akka dead letters encountered error.
Any help is really appreciated.
First off, in your WebSocket method, you need to use the provided ActorRef instead of sender. sender is probably something else in the hierarchy.
class ClientWebSocket(uuid: String, out: ActorRef) extends Actor {
import ClientWebSocket._
def receive = {
case SendUpdate(msg: String) =>
out ! s"Message is $msg"
}
}
Second, you are receiving dead letters in sendMessage because you are replying to your controller, which unfortunately is not an Actor.
The problem is that you cannot get hold of the ActorRef and since you don't know the name of the actor, you cannot use an ActorSelection. So you would need to make a WebSocket call to your own app, call it from web browser / JavaScript or do some hacking with the action to get to the actor.
Edit
Your ClientWebSocket could register (e.g. in preStart via ActorSelection or via an ActorRef as props parameter) with another actor that keeps a Map[String, ActorRef] reference to all websockets and in turn monitors them using death watch. That actor would then forward your SendUpdate to the correct websocket actor. Since you can only return Props in acceptWithActor, you are not able to form a real hierarchy.
The out parameter is an ActorRef, you will use it to send messages to the Client, therefore you need to keep it in your Actor.
object Client extends Controller {
def socket(uuid: String) = WebSocket.acceptWithActor[String, String] { request =>
// out is an actor to what the outbound messages should go to respond to remote client
out => ClientWebSocket.props(uuid, out)
}
}
Your ClientWebSocket actor is as follows:
class ClientWebSocket(uuid: String, out: ActorRef) extends Actor ...
And the companion object is as follows
object ClientWebSocket {
def props(uuid: String, out: ActorRef) = Props(classOf[ClientWebSocket], uuid, out)
}
In the ClientWenSocket you can Register/Deregister the Actor with a CentralWebSocketControl Actor as:
class ClientWebSocket(uuid: String, out: ActorRef) extends Actor {
#throws[Exception](classOf[Exception])
override def preStart(): Unit = {
super.preStart()
CentralWebSocketControl.instance ! RegisterMe(uuid)
}
#throws[Exception](classOf[Exception])
override def postStop(): Unit = {
CentralWebSocketControl.instance ! UnregisterMe(uuid)
super.postStop()
}
}
The CentralWebSocketControl Actor can be as:
class CentralWebSocketControl extends Actor {
val clientActors = scala.collection.mutable.HashMap[String, ActorRef]()
override def receive: Actor.Receive = {
case RegisterMe(uuid) =>
clientActors += uuid -> sender()
case UnregisterMe(uuid) =>
clientActors -= uuid
}
}
object CentralWebSocketControl {
val instance = Akka.system.actorOf(Props(classOf[CentralWebSocketControl]), name = "CentralWebSocketControl")
}
To send a message to a given ClientWebSocket identified by an uuid, you can send a message to CentralWebSocketControl that may delegate the message to the registered ClientWebSocket.
class CentralWebSocketControl extends Actor {
val clientActors = scala.collection.mutable.HashMap[String, ActorRef]()
override def receive: Actor.Receive = {
...
case SendUpdateTo(uuid: String, msg: String) =>
clientActors.get(uuid) match {
case Some(actor) =>
actor ! SendUpdate(msg)
case None =>
???
}
}
}
and finally
class ClientWebSocket(uuid: String, out: ActorRef) extends Actor {
override def receive: Receive = {
case SendUpdate(msg) =>
out ! msg
}
...
}
Consider these two traits:
trait Poked extends Actor {
override def receive = {
case Poke(port, x) => ReceivePoke(port, x)
}
def ReceivePoke(port: String, x: Any)
}
trait Peeked extends Actor {
override def receive = {
case Peek(port) => ReceivePeek(port)
}
def ReceivePeek(port: String)
}
Now consider I can create a new Actor that implements both traits:
val peekedpoked = actorRef(new Actor extends Poked with Peeked)
How do I compose the receive handlers? i.e., the receiver should be something like the following code, though "automatically generated" (i.e., all traits should compose):
def receive = (Poked.receive: Receive) orElse (Peeked.receive: Receive) orElse ...
You can use super[T] to reference members of particular super classes/traits.
For example:
trait IntActor extends Actor {
def receive = {
case i: Int => println("Int!")
}
}
trait StringActor extends Actor {
def receive = {
case s: String => println("String!")
}
}
class IntOrString extends Actor with IntActor with StringActor {
override def receive = super[IntActor].receive orElse super[StringActor].receive
}
val a = actorOf[IntOrString].start
a ! 5 //prints Int!
a ! "Hello" //prints String!
Edit:
In response to Hugo's comment, here's a solution that allows you to compose the mixins without having to manually wire their receives together. Essentially it involves a base trait with a mutable List[Receive], and each mixed-in trait calls a method to add its own receive to the list.
trait ComposableActor extends Actor {
private var receives: List[Receive] = List()
protected def registerReceive(receive: Receive) {
receives = receive :: receives
}
def receive = receives reduce {_ orElse _}
}
trait IntActor extends ComposableActor {
registerReceive {
case i: Int => println("Int!")
}
}
trait StringActor extends ComposableActor {
registerReceive {
case s: String => println("String!")
}
}
val a = actorOf(new ComposableActor with IntActor with StringActor).start
a ! 5 //prints Int!
a ! "test" //prints String!
The only thing to keep in mind is that the order of the receives should not be important, since you won't be able to easily predict which one is first in the chain, though you could solve that by using a mutable hashmap instead of a list.
You can use empty Receive in base actor class and chain receives in their definitions.
Sample for Akka 2.0-M2:
import akka.actor.Actor
import akka.actor.Props
import akka.event.Logging
import akka.actor.ActorSystem
class Logger extends Actor {
val log = Logging(context.system, this)
override def receive = new Receive {
def apply(any: Any) = {}
def isDefinedAt(any: Any) = false
}
}
trait Errors extends Logger {
override def receive = super.receive orElse {
case "error" => log.info("received error")
}
}
trait Warns extends Logger {
override def receive = super.receive orElse {
case "warn" => log.info("received warn")
}
}
object Main extends App {
val system = ActorSystem("mysystem")
val actor = system.actorOf(Props(new Logger with Errors with Warns), name = "logger")
actor ! "error"
actor ! "warn"
}