Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Play framework controller that streams responses

I have a controller that sends a chunked response:

def streamDatase2t(query:String): Action[AnyContent] = Action.async {
    req =>
        serivce.getIterator(query).map(res => {
            Ok.chunked(Source.apply(res))
        })
  }

When I try to inspect the returned content in the controller spec I get an exception:

  "return 200 response with the content of the iterator" in {
      when(serivce.getIterator
      (Matchers.any[Request.DatasetLoad],
        Matchers.any[ResponseFormat], Matchers.any[Int]))
        .thenReturn(Future.successful(new FakeIterable(List("One", "Two", "Three").iterator)))

      val fakeRequest = FakeRequest.apply("GET", s"/data")

      val result = Helpers.route(fakeApp, fakeRequest).get
      checkStatus(result, OK)
      contentAsString(result) // <-- exception here ! 
    }

Exception:

NoMaterializer cannot materialize
java.lang.UnsupportedOperationException: NoMaterializer cannot materialize
    at play.api.test.NoMaterializer$.materialize(Helpers.scala:732)
    at akka.stream.scaladsl.RunnableGraph.run(Flow.scala:629)
    at akka.stream.scaladsl.Source.runWith(Source.scala:106)
    at akka.stream.scaladsl.Source.runFold(Source.scala:117)
    at play.api.http.HttpEntity.consumeData(HttpEntity.scala:49)
    at play.api.http.HttpEntity.consumeData$(HttpEntity.scala:48)
    at play.api.http.HttpEntity$Chunked.consumeData(HttpEntity.scala:117)
    at play.api.test.ResultExtractors.contentAsBytes(Helpers.scala:381)
    at play.api.test.ResultExtractors.contentAsBytes$(Helpers.scala:379)
    at play.api.test.Helpers$.contentAsBytes(Helpers.scala:676)
like image 592
Adelin Avatar asked May 01 '26 16:05

Adelin


2 Answers

As the Exception states NoMaterializer cannot materialize you may need to add a Materializer:

implicit lazy val mat = ActorMaterializer()
implicit lazy val ec = instanceOf[ExecutionContext]
like image 179
pme Avatar answered May 04 '26 07:05

pme


contentAsString has NoMaterializer as the default argument

def contentAsString(of: Future[Result])(implicit timeout: Timeout, mat: Materializer = NoMaterializer): String

NoMaterializer just throws UnsupportedOperationException for everything so try providing your own

implicit val actorSystem = ActorSystem("test")
implicit val materializer = ActorMaterializer()

play-scala-streaming-example demonstrates how we might write a test for streaming controller.


Addressing the comment, consider the following two routes which illustrate the difference between a strict and non-strict (chunked, streamed) body

def nonStrictBody = Action {
  val source = Source.apply(List("woo", "h", "oo"))
  Ok.chunked(source)
}

def strictBody = Action {
  Ok("woohoo")
}

When calling contentAsString on a strict body, then materializer will not be used, hence NoMaterializer is sufficient

In 99% of cases, when running tests against the result body, you don't actually need a materializer since it's a strict body. So, rather than always requiring an implicit materializer, we use one if provided, otherwise we have a default one that simply throws an exception if used.

However when calling contentAsString on a chunked or streamed body, as it is the case in the nonStrictBody route, then we need to provide a proper Materializer.

like image 29
Mario Galic Avatar answered May 04 '26 06:05

Mario Galic



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!