VolatileRig
VolatileRig

Reputation: 2847

TestProbe not receiving messages from RouteSpec

I'm having issues when trying to "mock" out an Actor behind a Route. I want to be able to override and simulate functionality at test time, and I think TestProbe is the correct way to approach this.

However, I have not gotten a TestProbe to receive a single request. When using probe.expectMsg(request), the test fails with assertion failed: timeout (3 seconds) during expectMsg while waiting for GetCardRequest(12345). Removing the expectMsg and reply calls will still cause the test to fail due to Request was rejected at the check block. I'm expecting val result = request ~> routes ~> runRoute to hit the underlying TestProbe.

I feel like I'm just not understanding something simple about the setup! Thanks for the help in advance!

class MyRoutesSpec 
  extends WordSpec 
  with Matchers 
  with ScalaFutures 
  with ScalatestRouteTest
  with MyRoutes {
  lazy val routes = MyRoutes

  val probe = new TestProbe(system)
  override val cardLookupActor = probe.ref

  //TODO figure out how to get TestProbe to actually work!!
  "My Routes" should {
    "be able to get a card from a request" in {
      val cardRequest = GetCardRequest("12345")
      val cardRequestEntity = Marshal(cardRequest).to[MessageEntity].futureValue // futureValue is from ScalaFutures
      val request = Post("/getcard").withEntity(cardRequestEntity)
      val cardResponse = ClientCard("Hello", "World")

      val result = request ~> routes ~> runRoute

      probe.expectMsg(cardRequest)
      probe.reply(cardResponse)

      check {
        status should ===(StatusCodes.Created)
        contentType should ===(ContentTypes.`application/json`)
        entityAs[String] should ===("""{"cardName":"Hello", "cardType":"World"}""")
      } (result)
    }
  }
}


trait MyRoutes extends JsonSupport {

  // we leave these abstract, since they will be provided by the App
  implicit def system: ActorSystem

  lazy val log = Logging(system, classOf[MyRoutes])

  // other dependencies that Routes use
  def cardLookupActor: ActorRef

  // Required by the `ask` (?) method below
  implicit lazy val timeout = Timeout(5.seconds) 

  lazy val myRoutes: Route =
    pathPrefix("getcard") {
      concat(
        path(Segment) { id =>
          concat(
            get {
              val cardFuture: Future[ClientCard] =
                (cardLookupActor ? GetCardRequest(id = id)).mapTo[ClientCard]
            })
        })
    }
}

Upvotes: 0

Views: 250

Answers (1)

Ivan Stanislavciuc
Ivan Stanislavciuc

Reputation: 7275

Your tests works but it just proves that test probe is never called.

Check this test that passes OK

import akka.Done
import akka.actor.ActorRef
import akka.actor.ActorSystem
import akka.event.Logging
import akka.http.scaladsl.server._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.testkit.ScalatestRouteTest
import akka.testkit.TestProbe
import akka.util.Timeout
import org.scalatest.Matchers
import org.scalatest.WordSpec
import org.scalatest.concurrent.ScalaFutures
import akka.pattern.ask

import concurrent.duration._

class MyRoutesSpec extends WordSpec with Matchers with ScalaFutures with ScalatestRouteTest with MyRoutes {

  val probe = new TestProbe(system)
  override val cardLookupActor = probe.ref

  //TODO figure out how to get TestProbe to actually work!!
  "My Routes" should {
    "be able to get a card from a request" in {
      val cardRequest = "12345"
      val request = Get("/getcard/sss").withEntity(cardRequest)
      val cardResponse = "HelloWorld"

      val result = request ~> myRoutes ~> runRoute

      probe.expectMsg(Done)
      probe.reply(cardResponse)

      check {
        status should ===(StatusCodes.OK)
        entityAs[String] should ===("""HelloWorld""")
      }(result)
    }
  }
}

trait MyRoutes {

  // we leave these abstract, since they will be provided by the App
  implicit def system: ActorSystem

  lazy val log = Logging(system, classOf[MyRoutes])

  // other dependencies that Routes use
  def cardLookupActor: ActorRef

  // Required by the `ask` (?) method below
  implicit lazy val timeout = Timeout(5.seconds)

  lazy val myRoutes: Route =
    pathPrefix("getcard") {
      path(Segment) { _ =>
        get {
          complete((cardLookupActor ? Done).mapTo[String])
        }
      }
    }
}

What fixed

  • lazy val routes = MyRoutes is removed
  • You send Post but route expects Get
  • There is no segment passed in test, ie /getcard is not matched in pathPrefix("getcard") {path(Segment) {id => ???}}.
  • Call request ~> myRoutes ~> runRoute instead of request ~> routes ~> runRoute
  • I removed case classes just to be able to run it

Upvotes: 1

Related Questions