Search code examples
scalaakkaakka-streamakka-http

Why does this akka-http route test never complete successfully?


I've a simple route and some tests that success individually, but collectively fail with timeout. Any idea why?

val route = (requestHandler: ActorRef @@ Web) => {
   get {
     pathPrefix("apps") {
       pathEndOrSingleSlash {
         completeWith(implicitly[ToEntityMarshaller[List[String]]]) { callback =>
           requestHandler ! GetAppsRequest(callback)
         }
       } ~ path("stats") {
         completeWith(implicitly[ToEntityMarshaller[List[Stats]]]) { callback =>
           requestHandler ! GetStatsRequest(callback)
         }
       }
     } ~ path("apps" / Segment / "stats") { app =>
       completeWith(implicitly[ToEntityMarshaller[Stats]]) { callback =>
         requestHandler ! GetStatsForOneRequest(app, callback)
       }
     }
   }
 }

and tests:

val testProbe = TestProbe()
val testProbeActor = testProbe.ref
  .taggedWith[Web]

val timeout = 1.minute

"Route" should "respond to get apps request" in {
  implicit val routeTestTimout = RouteTestTimeout(timeout.dilated)
  Get("/apps") ~> route(testProbeActor) ~> check {

    testProbe.receiveOne(timeout) match {
      case GetAppsRequest(callback) => {
        callback(k8SProperties.apps)
      }
    }
    entityAs[List[String]] should contain("test")
  }
  testProbe.expectNoMessage(timeout)
}

it should "respond to get stats request for all apps" in {
  implicit val routeTestTimout = RouteTestTimeout(timeout.dilated)
  val app = "test"
  Get("/apps/stats") ~> route(testProbeActor) ~> check {

    testProbe.receiveOne(timeout) match {
      case GetStatsRequest(callback) => {
        callback(List(Stats(app, ChronoUnit.SECONDS, Nil)))
      }
      case other => fail(s"Unexpected message $other.")
    }
    entityAs[List[Stats]].size shouldBe (1)
    entityAs[List[Stats]].head.app shouldBe (app)
  }
  testProbe.expectNoMessage(timeout)
}

it should "respond to get stats request for one app" in {
  implicit val routeTestTimout = RouteTestTimeout(timeout.dilated)
  val app = "test"
  Get(s"/apps/$app/stats") ~> route(testProbeActor) ~> check {

    testProbe.receiveOne(timeout) match {
      case GetStatsForOneRequest(app, callback) => {
        callback(Stats(app, ChronoUnit.SECONDS, Nil))
      }
      case other => fail(s"Unexpected message $other.")
    }
    entityAs[Stats].app shouldBe (app)
  }
  testProbe.expectNoMessage(timeout)
}

Edit: Opened https://github.com/akka/akka-http/issues/1615


Solution

  • Working code, thanks to me.

    "Routes" should "respond to get apps request" in {
      testProbe.setAutoPilot((_: ActorRef, msg: Any) => {
        msg match {
          case GetAppsRequest(callback) => callback(List("test")); TestActor.KeepRunning
          case _ => TestActor.NoAutoPilot
        }
      })
    
      Get("/apps") ~> handler ~> check {
        entityAs[List[String]] should contain("test")
      }
    }
    

    Putting TestProbe inside check hangs forever probably because it creates a deadlock; test waits for callback to be called, and callback isn't called until the body is executed.

    Using autopilot sets up an "expectation" that can be fulfilled later thus avoiding the deadlock.