I'm working on a Spray API, with an Akka router to send the incoming messages on to a pool of actors for handling the logic. Now I want to write some tests for the API, but I'm struggling to find the right structure for the code. The API looks as follows at the moment:
import akka.actor.{ActorRef, ActorSystem, Props, Actor}
import akka.io.IO
import akka.routing.SmallestMailboxPool
import akka.util.Timeout
import akka.pattern.ask
import com.typesafe.config.ConfigFactory
import spray.json._
import spray.can.Http
import scala.concurrent.duration._
import spray.routing._
import spray.http._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Success
import scala.util.Failure
object implicits{
implicit val system = ActorSystem("ApiSystem")
implicit val timeout = Timeout(5.seconds)
implicit val conf = ConfigFactory.load()
// Custom case class for parsing JSON parameter.
case class Msg(key1:String, key2:String, key3:Int)
object JsonProtocol extends DefaultJsonProtocol {
implicit val msg = jsonFormat3(Msg)
}
case class PostMsg(msg:String)
case object PostSuccess
case class PostFailure(msg:String)
}
import implicits._
object MyApi extends App {
override def main(Args: Array[String]):Unit = {
// create and start our service actor
val service = system.actorOf(Props(new MyApiActor(system)), "MyApi-service")
IO(Http) ? Http.Bind(service, interface = conf.getString("http.host"), port = conf.getInt("http.port"))
}
}
class MyApiActor(system: ActorSystem) extends Actor with MyApiService {
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(myRoute)
}
// this trait defines our service behavior independently from the service actor
trait MyApiService extends HttpService {
import implicits.JsonProtocol._
var actorPool = system.actorOf(SmallestMailboxPool(conf.getInt("actor-number")).props(Props(new HandlingActor(conf))), "msgRouter")
val myRoute =
path("msg") {
post {
entity(as[String]) { obj =>
try{
// if this parsing succeeds, the posted msg satisfies the preconditions set.
obj.parseJson.convertTo[Msg]
} catch {
case e: DeserializationException => {
complete(HttpResponse(status=StatusCodes.BadRequest, entity="Invalid json provided."))
}
case e: Exception => {
complete(HttpResponse(status=StatusCodes.InternalServerError, entity="Unknown internal server error."))
}
}
onComplete(actorPool ? PostMsg(obj)) {
case Success(value) => complete(HttpResponse(status = StatusCodes.OK, entity = "Pushed Msg"))
case Failure(value) => complete(HttpResponse(status = StatusCodes.InternalServerError, entity = "Handling failed."))
}
}
}
}
}
What I would like to test is the response of the API to various HTTP messages (i.e. correct calls, incorrect calls etc.). The logic in the handling actor is simply to push the message to a Kafka bus, so I would like to "mock" this behaviour (i.e. be able to test the API response if this push succeeds and also what happens when this push fails).
The thing I'm struggling with most at the moment is how to setup the test. For now, I am setting up the API using the same commands as in the main method shown, but I need to specify a different actorPool, as I don't want any messages to actually be pushed. How should I best go about achieving such tests?
I am using Scalatest, with the Akka and Spray testkit. (plus possibly mockito for mocking if necessary)
I have few suggestions to make your testing easier:
Do not create the actor pool in your trait. Instead inject the ActorRef from the ActorPool using a def instead of a val in the route. Then it will be easier to inject your actorPool TestProbe() to test. For example (I have not tried/compiled this code):
class MyApiActor extends Actor with MyApiService {
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
val actorPool = context.actorOf(SmallestMailboxPool(conf.getInt("actor-number")).props(Props(new HandlingActor(conf))), "msgRouter")
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(myRoute(actorPool))
}
// this trait defines our service behavior independently from the service actor
trait MyApiService extends HttpService {
import implicits.JsonProtocol._
def myRoute(actorPool: ActorRef) =
path("msg") {
post {
entity(as[String]) { obj =>
try{
// if this parsing succeeds, the posted msg satisfies the preconditions set.
obj.parseJson.convertTo[Msg]
} catch {
case e: DeserializationException => {
complete(StatusCodes.BadRequest, "Invalid json provided.")
}
case e: Exception => {
complete(StatusCodes.InternalServerError, "Unknown internal server error.")
}
}
onComplete(actorPool ? PostMsg(obj)) {
case Success(value) => complete(StatusCodes.OK, "Pushed Msg")
case Failure(value) => complete(StatusCodes.InternalServerError, "Handling failed.")
}
}
}
}
}
Then the test can look like this:
class HttpListenerSpec extends WordSpecLike with Matchers with ScalatestRouteTest with MyApiService {
"An HttpListener" should {
"accept GET at /msg" in {
val actorPool = TestProbe()
(stuff for responding with TestProbe()...)
Get("/msg") ~> myRoute(actorPool.ref) ~> check {
status shouldBe OK
val response = responseAs[String]
assert(...)
}
}
}
}
Also, as a final suggestion. There are implicit conversions that integrate spray json and spray so you can do entity(as[Msg]). Look for the following:
import spray.httpx.marshalling._
import spray.httpx.unmarshalling._
import spray.httpx.SprayJsonSupport._
import MsgJsonProtocol._
Related
I am trying to get "ask" working for Akka Typed. I have followed examples online, and I thought I pretty much replicated what they showed, but I'm getting an compiler error when I try to evaluate the response from the "ask". Here's my minimal reproducible example.
SuperSimpleAsker is an actor that is requesting a "widget" from the MyWidgetKeeper actor. The response is a string representing the widget's id. All I'm trying to do so far is log the received widget id as a "Success" message, and will add more stuff to do with the id later. When the SuperSimpleAsker is created, the ActorRef of the MyWidgetKeeper is passed in. I have left out the Main program that creates the actors to keep the code simple.
The error that I get is:
type mismatch;
found : Unit
required: widgets.SuperSimpleAsker.Request
This error occurs on both of the logger.* lines (inside of the Case Failure and Case Success blocks toward the end of the code listing).
I don't understand what part of the code is requiring a "widgets.SuperSimpleAsker.Request" object or why.
package widgets
import scala.concurrent.duration.DurationInt
import scala.util.{Failure, Success}
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.{ActorRef, Behavior}
import akka.util.Timeout
import com.typesafe.scalalogging.LazyLogging
object MyWidgetKeeper {
sealed trait Request
case class GetWidget(replyTo: ActorRef[Response]) extends Request
sealed trait Response
case class WidgetResponse(widget: String) extends Response
def apply(): Behavior[Request] =
new MyWidgetKeeper().myWidgetKeeper()
}
class MyWidgetKeeper {
import MyWidgetKeeper._
def myWidgetKeeper(): Behavior[Request] = {
Behaviors.receive { (context, message) =>
message match {
case GetWidget(replyTo) =>
replyTo ! WidgetResponse("12345")
Behaviors.same
}
}
}
}
object SuperSimpleAsker {
sealed trait Request
case object DoStuff extends Request
def apply(widgetKeeper: ActorRef[MyWidgetKeeper.Request]): Behavior[Request] =
new SuperSimpleAsker(widgetKeeper).simpleAsker()
}
class SuperSimpleAsker(widgetKeeper: ActorRef[MyWidgetKeeper.Request]) extends LazyLogging{
import SuperSimpleAsker._
import widgets.MyWidgetKeeper.GetWidget
private def simpleAsker(): Behavior[Request] = {
Behaviors.receive { (context, message) =>
message match {
case DoStuff =>
logger.info(f"Doing stuff")
implicit val timeout = Timeout(2000 millis)
context.ask(widgetKeeper, GetWidget)
{
case Failure(exception) =>
logger.error(f"Failed: ${exception.getMessage}")
case Success(response: MyWidgetKeeper.Response) =>
response match {
case MyWidgetKeeper.WidgetResponse(id) =>
logger.info(f"Success: Got Widget# $id")
// Do some more stuff with the widget id
}
}
Behaviors.same
}
}
}
}
In Akka Typed's context.ask, the passed function converts the successful or failed ask into a message which gets sent to the actor, ideally without performing a side effect.
So your SuperSimpleAsker will have to add messages that the ask can be converted to:
object SuperSimpleAsker {
sealed trait Request
case object DoStuff extends Request
case class WidgetResponseFor(widgetId: String) extends Request
case object NoWidgetResponse extends Request
def apply(widgetKeeper: ActorRef[MyWidgetKeeper.Request]): Behavior[Request] =
new SuperSimpleAsker(widgetKeeper).simpleAsker()
}
class SuperSimpleAsker(widgetKeeper: ActorRef[MyWidgetKeeper.Request]) extends LazyLogging{
import SuperSimpleAsker._
import widgets.MyWidgetKeeper.GetWidget
private def simpleAsker(): Behavior[Request] = {
Behaviors.receive { (context, message) =>
message match {
case DoStuff =>
logger.info(f"Doing stuff")
implicit val timeout = Timeout(2000 millis)
context.ask(widgetKeeper, GetWidget)
{
case Failure(_) => // there's actually only one possible exception: timed out
NoWidgetResponse
case Success(response: MyWidgetKeeper.Response) =>
WidgetResponseFor(response.widget)
}
Behaviors.same
case WidgetResponseFor(id) =>
logger.info(f"Success: Got Widget# $id")
// Do stuff with the widget id
Behaviors.same
case NoWidgetResponse =>
logger.error("Failed")
Behaviors.same
}
}
}
}
I am new to actors, I am learning, how to test actors using TestActorRef
My actor code:
package actors
import actors.GreetingActor.Hi
import akka.actor.Actor
object GreetingActor {
case class Hi()
}
class GreetingActor extends Actor {
var greeting = ""
override def receive: Receive = {
case Hi() =>
greeting = "Hi"
case _ =>
throw new IllegalArgumentException("not supported message")
}
override def postRestart(reason: Throwable) = {
println(s"actor is restarted because of ${reason.getMessage}")
}
}
I am sure that everything works as I want in this code, but I can't show it in test. Especially I can't show that one of the most important thing, that my actor crashed. The test is very simple and obvious I send message that is not Hi() and should track somehow that actor crashed with IllegalArgumentException. My current test code:
package actors
import actors.GreetingActor.Hi
import akka.actor.ActorSystem
import akka.testkit.{TestActorRef, TestKit}
import org.scalatest.{MustMatchers, WordSpecLike}
class GreetingActorTest extends TestKit(ActorSystem("testsystem")) with WordSpecLike
with MustMatchers with StopSystemAfterAll {
"A Hello Actor" must {
"change state when it receives a message, single threaded" in {
val greetingActor = TestActorRef[GreetingActor]
greetingActor ! Hi()
greetingActor.underlyingActor.greeting mustBe "Hi"
}
"throw exception when it received unknown message, single threaded" in {
val greetingActor = TestActorRef[GreetingActor]
greetingActor ! "hi"
//some code that checks that actor crashed
}
}
}
The question is how can I track in test that my actor crashed using TestActorRef? Appreciate any help.
Change your test to the following:
"throw exception when it received unknown message, single threaded" in {
assertThrows[IllegalArgumentException] {
val greetingActor = TestActorRef[GreetingActor]
greetingActor.receive("hi")
}
}
Per the actor docs, you need to use receive so the exception doesn't get swallowed:
http://doc.akka.io/docs/akka/current/scala/testing.html#The_Way_In-Between__Expecting_Exceptions
I am building some JSON HTTP services using spray and I am having some problems testing a RejectionHandler. If I start the application running the command sbt run and make the request, the RejectionHandler process the MalformedRequestContentRejection as expected but I am getting an IllegalArgumentException when running the tests even with the route sealed. In the other hand, the MethodRejection works fine. The JSON validation is done using require
The next example is based in the spray-template repository branch on_spray-can_1.3_scala-2.11 with a POST endpoint and the new tests. I've made a fork with the entire example here
Notice the use of a case clase for deserialize JSONs, the use of the require method for validation and the declaration of an implicit RejectionHandler.
package com.example
import akka.actor.Actor
import spray.routing._
import spray.http._
import StatusCodes._
import MediaTypes._
import spray.httpx.SprayJsonSupport._
class MyServiceActor extends Actor with MyService {
def actorRefFactory = context
def receive = runRoute(myRoute)
}
case class SomeReq(field: String) {
require(!field.isEmpty, "field can not be empty")
}
object SomeReq {
import spray.json.DefaultJsonProtocol._
implicit val newUserReqFormat = jsonFormat1(SomeReq.apply)
}
trait MyService extends HttpService {
implicit val myRejectionHandler = RejectionHandler {
case MethodRejection(supported) :: _ => complete(MethodNotAllowed, supported.value)
case MalformedRequestContentRejection(message, cause) :: _ => complete(BadRequest, "requirement failed: field can not be empty")
}
val myRoute =
pathEndOrSingleSlash {
post {
entity(as[SomeReq]) { req =>
{
complete(Created, req)
}
}
}
}
}
This is are the test implemented using spray-testkit. The last one expects a BadRequest but the test fails with an IllegarArgumentException.
package com.example
import org.specs2.mutable.Specification
import spray.testkit.Specs2RouteTest
import spray.http._
import StatusCodes._
import spray.httpx.SprayJsonSupport._
class MyServiceSpec extends Specification with Specs2RouteTest with MyService {
def actorRefFactory = system
"MyService" should {
"leave GET requests to other paths unhandled" in {
Get("/kermit") ~> myRoute ~> check {
handled must beFalse
}
}
"return a MethodNotAllowed error for PUT requests to the root path" in {
Put() ~> sealRoute(myRoute) ~> check {
status should be(MethodNotAllowed)
responseAs[String] === "POST"
}
}
"return Created for POST requests to the root path" in {
Post("/", new SomeReq("text")) ~> myRoute ~> check {
status should be(Created)
responseAs[SomeReq] === new SomeReq("text")
}
}
/* Failed test. Throws IllegalArgumentException */
"return BadRequest for POST requests to the root path without field" in {
Post("/", new SomeReq("")) ~> sealRoute(myRoute) ~> check {
status should be(BadRequest)
responseAs[String] === "requirement failed: field can not be empty"
}
}
}
}
I am missing something?
Thanks in advance!
Your SomeReq class is being eagerly instantiated in the Post("/", new SomeReq("")) request builder and the require method is invoked as soon as the class is instantiated.
To get around this try using the following instead:
import spray.json.DefaultJsonProtocol._
Post("/", JsObject("field" → JsString(""))) ~> sealRoute(myRoute) ~> check {
status should be(BadRequest)
responseAs[String] === "requirement failed: field can not be empty"
}
Let say I have an actor called TestedActor wich is able to save an Int value and send it back as follow:
class TestedActor extends Actor {
override def receive = receive(0)
def receive(number: Int): Receive = {
case new_number: Int => context.become(receive(new_number))
case ("get", ref: ActorRef) => ref ! number
}
}
In my test, I would like to be able to get this Integer and test it.
So i've been thinking about creating something like:
class ActorsSpecs extends FlatSpec with Matchers {
case class TestingPositive(testedActor: ActorRef) extends Actor {
override def receive = {
case number: Int => checkNumber(number)
case "get" => testedActor ! ("get", self)
}
def checkNumber(number: Int) = {
number should be > 0
}
}
implicit val system = ActorSystem("akka-stream")
implicit val flowMaterializer = ActorMaterializer()
val testedActor = system.actorOf(Props[TestedActor], name = "testedActor")
val testingActor = system.actorOf(Props(new TestingPositive(testedActor)), name = "testingActor")
testingActor ! "get"
}
This way, i'm able to create this TestingPositive actor, to get the number in the TestedActor and test it in checkNumber.
It seems to be working well, my problem is :
When the test fail, it raise an exception in the actor thread, I can see what went wrong in the console, but it is still saying that all my tests succeeded. Because (I think) the main thread is not aware of this failure.
Does someone knows an easier way than all of this TestingActor stuff?
Or any solution to tell the main thread that it failed?
Thank you
Take a look at using TestKit docs here. You can write a much simpler test for your actor. See how you like this test:
import akka.actor.{Props, ActorSystem}
import akka.testkit.{TestProbe, TestKit}
import org.scalatest.{BeforeAndAfterAll, FlatSpecLike, ShouldMatchers}
class ActorSpecs extends TestKit(ActorSystem("TestSystem"))
with FlatSpecLike
with ShouldMatchers
with BeforeAndAfterAll {
override def afterAll = {
TestKit.shutdownActorSystem(system)
}
def fixtures = new {
val caller = TestProbe()
val actorUnderTest = system.actorOf(Props[TestedActor], name = "testedActor")
}
"The TestedActor" should "pass a good test" in {
val f = fixtures; import f._
caller.send(actorUnderTest, 42)
caller.send(actorUnderTest, ("get", caller.ref))
caller.expectMsg(42)
}
"The TestedActor" should "fail a bad test" in {
val f = fixtures; import f._
caller.send(actorUnderTest, 42)
caller.send(actorUnderTest, ("get", caller.ref))
caller.expectMsg("this won't work")
}
}
Also, you should know about sender. While your get certainly works, a cleaner approach might be to reply to the sending actor:
def receive(number: Int): Receive = {
case new_number: Int => context.become(receive(new_number))
case "get" => sender ! number
}
And the test becomes:
"The TestedActor" should "pass a good test" in {
val f = fixtures; import f._
caller.send(actorUnderTest, 42)
caller.send(actorUnderTest, "get")
caller.expectMsg(42)
}
And finally, I'll shamelessly plug my recent blog post about maintaining an akka code base with my team. I feel morally obligated to give a new hAkker an opportunity to read it. :)
I'm fairly new to testing Akka and have been stuck trying to simply verify that a message is sent to a subscriber when a publish message is sent to an Akka mediator from akka.contrib.pattern.DistributedPubSubMediator. I'd like to understand how to get his working before I branch into verifying actual business logic.
import akka.actor.{ ActorRef, Actor }
import akka.contrib.pattern.DistributedPubSubExtension
import akka.contrib.pattern.DistributedPubSubMediator.{ Subscribe, Publish }
import akka.testkit.{ ImplicitSender, TestKit }
import org.specs2.mutable.{ After, Specification }
import queues.{ Publisher, Subscriber }
import scala.concurrent.duration._
class PubSubActorSupport extends TestKit(ActorSystem("PubSubTest")) with ImplicitSender with After {
override def after = system.shutdown()
lazy val subscriberRef = system.actorOf(Props(classOf[Subscriber]))
lazy val publisherRef = system.actorOf(Props(classOf[Publisher]))
lazy val testActorRef = system.actorOf(Props(classOf[MyTestActor]))
}
class MyTestActor extends Actor {
def receive: Receive = { case x => () }
}
object EventsTest extends Specification {
"Publish/Subscriber Actors" should {
"work" in new PubSubActorSupport {
subscriberRef ! ("topic", testActorRef)
publisherRef ! ("topic", "Hello!")
// assert that MyTestActor received "Hello!" from the publisher actor
expectMsg(500 milli, "Hello!")
}
}
}
I figure I'm not using expectMsg correctly. My Subscriber/Publisher actors are below.
trait Mediator extends Actor {
val mediator = DistributedPubSubExtension(context.system).mediator
}
class Publisher extends Mediator {
def receive = {
case (topic: String, msg: Any) => mediator ! Publish(topic, msg)
}
}
class Subscriber extends Mediator {
def receive = {
case (topic: String, ref: ActorRef) => mediator ! Subscribe(topic, ref)
}
}
Check this sample for testing SubPub pattern
https://github.com/JAVEO/clustered-chat/blob/master/test/actors/UserSocketSpec.scala
expectMsg just verify message that is send to "sender". And if you want to verify a message sent to a subscribers, write like this (From the sample above)
val mediator = DistributedPubSubExtension(system).mediator
val chatMember1 = TestProbe()
mediator ! Subscribe(topic, chatMember1.ref)
//... Call the publisher to publish message
chatMember1.expectMsg(ChatMessage(UserId, message))