Akka test case assertion failure - scala

I am trying to write test for an actor which sends:
sender() ! Status.Failure(message)
I have written the test case as:
class TestActorSpec
extends TestKit(ActorSystem("system"))
with ImplicitSender
with WordSpecLike
with Matchers {
implicit val executionContext: ExecutionContextExecutor = system.dispatcher
implicit val futureDuration: FiniteDuration = 60.seconds
val timeout: FiniteDuration = 5.seconds
val testActorRef: ActorRef = system.actorOf(Props(new TestActor()), "test-actor")
val futureResult: Future[Any] = (testActorRef ? "Exception")(timeout)
"TestSpec" should {
"send the exception in {
expectMsgType[Status.Failure]
}
}
}
But i am getting the test failure with reason:
[info] java.lang.AssertionError: assertion failed: timeout (3 seconds) during expectMsgClass waiting for class akka.actor.Status$Failure
[info] at scala.Predef$.assert(Predef.scala:223)

If you want to receive Status.Failure as a message, you should not use ask pattern ? and use tell ! instead.
The reason is that ask pattern will consume the Failure and pass back a failed Future. In case of ask pattern, you need to assert a failed Future.
From Scala docs of akka.actor.Status.Failure
This class/message type is preferably used to indicate failure of some operation performed.
As an example, it is used to signal failure with AskSupport is used (ask/?).

Related

Play framework akka stream websocket handling message get sent to deadletters

I'm trying to wrap my head around akka streams and the way to handle web sockets, but some things are quite clear to me.
For starters, I'm trying to accomplish one-way communication from some client to the server and communication between the same server and some other client.
client1 -----> Server <------> client2
I was looking at the example provided here.
The resulting code looks something like this:
1) starting with the controller
class Test #Inject()(#Named("connManager") myConnectionsManager: ActorRef, cc: ControllerComponents)
(implicit val actorSystem: ActorSystem,
val mat: Materializer,
implicit val executionContext: ExecutionContext)
extends AbstractController(cc) {
private def wsFutureFlow(id: String): Future[Flow[String, String, NotUsed]] = {
implicit val timeout: Timeout = Timeout(5.seconds)
val future = myConnectionsManager ? CreateRemote(id)
val futureFlow = future.mapTo[Flow[String, String, NotUsed]]
futureFlow
}
private def wsFutureLocalFlow: Future[Flow[String, String, NotUsed]] = {
implicit val timeout: Timeout = Timeout(5.seconds)
val future = myConnectionsManager ? CreateLocal
val futureFlow = future.mapTo[Flow[String, String, NotUsed]]
futureFlow
}
def ws: WebSocket = WebSocket.acceptOrResult[String, String] {
rh =>
wsFutureFlow(rh.id.toString).map { flow =>
Right(flow)
}
}
def wsLocal: WebSocket = WebSocket.acceptOrResult[String, String] {
_ =>
wsFutureLocalFlow.map { flow =>
Right(flow)
}
}
}
As for the connection manager actor. That would be the equivalent of the UserParentActor from the example.
class MyConnectionsManager #Inject()(childFactory: MyTestActor.Factory)
(implicit ec: ExecutionContext, mat: Materializer) extends Actor with InjectedActorSupport {
import akka.pattern.{ask, pipe}
implicit val timeout: Timeout = Timeout(2.seconds)
override def receive: Receive = {
case CreateRemote(x) =>
val child = injectedChild(childFactory(), s"remote-$x")
context.watch(child)
privatePipe(child)
case CreateLocal =>
val child = injectedChild(childFactory(), "localConnection")
context.become(onLocalConnected(child))
privatePipe(child)
case Terminated(child) =>
println(s"${child.path.name} terminated...")
}
def onLocalConnected(local: ActorRef): Receive = {
case CreateRemote(x) =>
val child = injectedChild(childFactory(), s"remote-$x")
context.watch(child)
privatePipe(child)
case x: SendToLocal => local ! x
}
private def privatePipe(child: ActorRef) = {
val future = (child ? Init).mapTo[Flow[String, String, _]]
pipe(future) to sender()
() // compiler throws exception without this: non-unit value discarded
}
}
And the MyTestActor looks like this:
class MyTestActor #Inject()(implicit mat: Materializer, ec: ExecutionContext) extends Actor {
val source: Source[String, Sink[String, NotUsed]] = MergeHub.source[String]
.recoverWithRetries(-1, { case _: Exception => Source.empty })
private val jsonSink: Sink[String, Future[Done]] = Sink.foreach { json =>
println(s"${self.path.name} got message: $json")
context.parent ! SendToLocal(json)
}
private lazy val websocketFlow: Flow[String, String, NotUsed] = {
Flow.fromSinkAndSourceCoupled(jsonSink, source).watchTermination() { (_, termination) =>
val name = self.path.name
termination.foreach(_ => context.stop(self))
NotUsed
}
}
def receive: Receive = {
case Init =>
println(s"${self.path.name}: INIT")
sender ! websocketFlow
case SendToLocal(x) =>
println(s"Local got from remote: $x")
case msg: String => sender ! s"Actor got message: $msg"
}
}
What I don't understand, apart from how sinks and sources actually connect to the actors, is the following. When I start up my system, I send a few messages to the actor. However, after I close the connection to an actor named remote, and continue sending messages to the one called "localConnection", the messages get sent to DeadLetters:
[info] Done compiling.
[info] 15:49:20.606 - play.api.Play - Application started (Dev)
localConnection: INIT
localConnection got message: test data
Local got from remote: test data
localConnection got message: hello world
Local got from remote: hello world
remote-133: INIT
remote-133 got message: hello world
Local got from remote: hello world
remote-133 got message: hello from remote
Local got from remote: hello from remote
[error] 15:50:24.449 - a.a.OneForOneStrategy - Monitored actor [Actor[akka://application/user/connManager/remote-133#-998945083]] terminated
akka.actor.DeathPactException: Monitored actor [Actor[akka://application/user/connManager/remote-133#-998945083]] terminated
deadLetters got message: hello local
I assume this is because of the exception thrown... Can anyone explain to me as to why the message gets sent to DeadLetters?
Apart from that, I would like to know why I keep getting a compiler exception without the "()" returned at the end of privatePipe?
Also, should I be doing anything differently?
I realised that the exception was being thrown because I forgot to handle the Terminated message in the new behaviour of the MyConnectionsManager actor.
def onLocalConnected(local: ActorRef): Receive = {
case CreateRemote(x) =>
val child = injectedChild(childFactory(), s"remote-$x")
context.watch(child)
privatePipe(child)
case Terminated(child) => println(s"${child.path.name} terminated...")
case x: SendToLocal => local ! x
}
It seems to be working now.

How to wait for Akka actor to start during tests?

I have a test that needs to make an assertion on something that happens during an actor's preStart(), but I haven't figured out how to wait until that happens, and sometimes it doesn't happen before the assertion is made (and sometimes it does). I have tried this:
EventFilter.debug(start = "started", occurrences = 1).assertDone(10.seconds)
but I get an error message when using it:
java.lang.AssertionError: assertion failed: 1 messages outstanding on DebugFilter(None,Left(started),false)
You could place the creation of the actor inside an intercept block:
import akka.actor._
import akka.testkit.EventFilter
import com.typesafe.config.ConfigFactory
class MyActor extends Actor with ActorLogging {
override def preStart(): Unit = {
log.debug("started MyActor...")
}
def receive = {
case m => log.debug(s"Received this message: $m")
}
}
object MyActor {
def props() = Props[MyActor]
}
object EventFilterTest extends App {
implicit val system = ActorSystem("testsystem", ConfigFactory.parseString("""
akka.loggers = ["akka.testkit.TestEventListener"]
akka.loglevel = "DEBUG"
"""))
EventFilter.debug(start = "started", occurrences = 1) intercept {
val myActor = system.actorOf(MyActor.props)
myActor ! "cows"
}
}
Running the above code produces the following output:
[DEBUG] [...] [run-main-0] [EventStream(akka://testsystem)] logger log1-TestEventListener started
[DEBUG] [...] [run-main-0] [EventStream(akka://testsystem)] Default Loggers started
[DEBUG] [...] [testsystem-akka.actor.default-dispatcher-5] [akka://testsystem/user/$a] Received this message: cows
The intercept "catches" the debug statement in the actor's preStart hook.

Lagom service does not responding when it receives Source

I'm playing with Lagom and created service receiving Source as input and returning case class object:
import akka.NotUsed
import akka.stream.scaladsl.Source
import com.lightbend.lagom.scaladsl.api.{Service, ServiceCall}
import play.api.libs.json.{Format, Json}
trait TestService extends Service {
def test(): ServiceCall[Source[String, NotUsed], ResultData]
override final def descriptor = {
import Service._
named("DocsStore")
.withCalls(
call(test())
)
}
}
case class ResultData(uploadId: String, length: Long)
object ResultData {
implicit val format: Format[ResultData] = Json.format[ResultData]
}
Service implementation is:
class TestServiceImpl()(
implicit val materializer: Materializer,
implicit val ec: ExecutionContext
) extends TestService {
val logger = Logger(getClass.getName)
override def test(): ServiceCall[Source[String, NotUsed], ResultData] = ServiceCall{ source=>
source.runForeach(s=>logger.info(s"String $s")).map(_=>ResultData("TestResult", 12))
}
}
When I call this service from Play application's controller:
def test = Action.async { req=>
testService.test().invoke(Source("A"::"B"::"C"::Nil)).map(rd=>Ok(Json.toJson(rd)))
}
"runForeach" on service side successfully prints A, B, C but service itself does not return any result value (ResultData("TestResult", 12) is expected) causing Play application throw exception:
play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[DeserializationException: No content to map due to end-of-input
at [Source: akka.util.ByteIterator$ByteArrayIterator$$anon$1#309c63af; line: 1, column: 0]]]
at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:293)
at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:220)
at play.api.GlobalSettings$class.onError(GlobalSettings.scala:160)
at play.api.DefaultGlobal$.onError(GlobalSettings.scala:188)
at play.api.http.GlobalSettingsHttpErrorHandler.onServerError(HttpErrorHandler.scala:100)
at play.core.server.netty.PlayRequestHandler$$anonfun$2$$anonfun$apply$1.applyOrElse(PlayRequestHandler.scala:100)
at play.core.server.netty.PlayRequestHandler$$anonfun$2$$anonfun$apply$1.applyOrElse(PlayRequestHandler.scala:99)
at scala.concurrent.Future$$anonfun$recoverWith$1.apply(Future.scala:346)
at scala.concurrent.Future$$anonfun$recoverWith$1.apply(Future.scala:345)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
How this could be fixed?
This happens because Lagom interprets the completion of the stream as a signal to close the connection. The connection is closed before the response can be sent.
This has been raised as an issue in GitHub: https://github.com/lagom/lagom/issues/814
A possible workaround is to leave the stream open until the response is received, as demonstrated in the documentation on testing streaming services:
// Use a source that never terminates (concat Source.maybe) so we
// don't close the upstream, which would close the downstream
val input = Source("A"::"B"::"C"::Nil).concat(Source.maybe)
However, if using this strategy, the service implementation will also need to be changed, as the implementation in the question above only sends the response when the stream is completed. Instead, you'll need to design into your protocol an explicit completion message that signals to the service to send the response.

what should be the supervision strategy for AskTimeOutException

I am new to supervision in akka i want to know what supervision strategy is good when we get a ask timeout exception, what is more appropriate Restart or Resume
here is the sample code
class ActorA extends Actor{
override val supervisorStrategy = OneForOneStrategy(
maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
case _:AskTimeoutException => ??? (Resume/Restart)
case _:Exception => Restart
}
val actorB =context.actorof ...//actor creation code
implicit val timeout = Timeout(interval , SECONDS)
val future = ask(actorB, MessageB).mapTo[Boolean] //what if actorB does not reply withing the time and AskTimeoutException is thrown the what should be the supervision strategy
var response = Await.result(future, timeout.duration)
}
please guide me ,Thanks

AskTimeoutException: on spray-can server stop

I am trying to stop spray-can web server with the following code:
implicit val timeout = Timeout(10 seconds)
val future = ask(IO(Http)(system), Http.Unbind(10 second))
Await.result(future, Duration.Inf)
but unfortunatelly I receive the following exception:
[error] AskTimeoutException: : Timed out (AskSupport.scala:334)
[error]
akka.pattern.PromiseActorRef$$anonfun$1.apply$mcV$sp(AskSupport.scala:334)
[error] akka.actor.Scheduler$$anon$11.run(Scheduler.scala:118) [error]
akka.actor.LightArrayRevolverScheduler$TaskHolder.executeTask(Scheduler.scala:455)
[error]
akka.actor.LightArrayRevolverScheduler$$anon$12.executeBucket$1(Scheduler.scala:407)
[error]
akka.actor.LightArrayRevolverScheduler$$anon$12.nextTick(Scheduler.scala:411)
[error]
akka.actor.LightArrayRevolverScheduler$$anon$12.run(Scheduler.scala:363)
What am I doing wrong?
The problem is you are sending the Http.Unbind message to the wrong actor (i.e. the manager actor for the IO extension - in this case, Http).
You have to send the Http.Unbind message to the HttpListener (this is the actor that replies to the Http.Bind message with an Http.Bound message). The following example sends Http.Bind to the manager actor and Http.Unbind to the HttpListener:
class TestActor extends Actor {
override def preStart = {
IO(Http) ! Http.Bind(self, interface = "localhost", port = 8080)
}
def receive = {
case Http.Bound(_) =>
println("bound")
sender ! Http.Unbind(10 seconds)
case Http.Unbound =>
println("unbound")
context.stop(self)
}
}
More information can be found in the documentation section on starting and stopping.