0

I am writing an integration test using the Scala Play framework.

I in the controller I have a function that looks like this:

def myEndpoint: AnyAction = myActionProvider.securedEndpoint("myEndpoint") { implicit request =>
    // Business logic
    Ok("")
}

myActionProvider.securedEndpoint chains together some Action builders using andThen() and returns an ActionBuilder.

In my integration test I have the following:

val fakeRequest: FakeRequest = buildFakeRequest()
myController.myEndpoint.apply(fakeRequest)

The fake request contains a generated security token and other headers required by our app.

I am able to break inside myActionProvider.securedEndpoint and follow the execution. However, I am not able to break inside any of the invokeBlock methods, nor inside the business logic of the controller. Logging shows that those pathways are never executed.

If I save the result of myController.myEndpoint.apply(fakeRequest) to a variable, its type is Accumulator[ByteString, Result]. It looks like this contains the chained Action builders and the business logic code block, but Play never executes it.

The code throws no exceptions and nothing is output to the console.

I was wondering if it was an error with my test syntax. I've also tried the following, to no avail:

  • myController.myEndpoint { fakeRequest }
  • myController.myEndpoint()(fakeRequest)

And yes, the code works in a "regular" e.g. non-testing environment :)

Colin Strong
  • 118
  • 1
  • 9

1 Answers1

1

I created a working example on how to test a Controller here. It is based on Play 2.6.15 with Guice and is using scalatestplus-play library.

libraryDependencies ++= Seq(
  "org.scalatestplus.play" %% "scalatestplus-play" % "3.1.2" % "test"
)

Here is a controller with two actions:

import javax.inject.Inject
import play.api.mvc._

class ApplicationController @Inject()(controllerComponents: ControllerComponents)
  extends AbstractController(controllerComponents) {

  def foo(): Action[AnyContent] = Action { request =>
    Ok(s"I am listening at ${request.uri}")
  }

  def bar: Action[String] = Action(parse.tolerantText) { request =>
    Ok(s"Received body of size: ${request.body.length}")
  }
}

Actions with explicit body parsers do indeed return an Accumulator object. Interestingly, actions with default body parser returns a Future.

In both cases though, the play-test library provides helpers for extracting result body, status and other from both types of results.

import akka.stream.Materializer
import akka.util.ByteString
import org.scalatestplus.play._
import play.api.http.Status
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.libs.streams.Accumulator
import play.api.mvc._
import play.api.test.Helpers._
import play.api.test._

import scala.concurrent.Future

class ApplicationControllerTest extends PlaySpec {

  "ApplicationController" should {

    val application = new GuiceApplicationBuilder().build()
    val controller = application.injector.instanceOf[ApplicationController]

    "return correct response for foo" in {
      val result: Future[Result] = controller.foo().apply(FakeRequest("GET", "/testUriFoo"))
      contentAsString(result) mustBe "I am listening at /testUriFoo"
      status(result) mustBe Status.OK
    }

    "return correct response for bar" in {
      implicit val mat: Materializer = application.materializer
      val fakeRequest = FakeRequest("POST", "/testUriBar").withTextBody("123456789")
      val result: Accumulator[ByteString, Result] = controller.bar().apply(fakeRequest)
      contentAsString(result) mustBe "Received body of size: 9"
      status(result) mustBe Status.OK
    }

  }

}

Resources used:

ygor
  • 1,726
  • 1
  • 11
  • 23
  • 1
    Thanks. From the resources you provided I also learned that calling `controller.bar()(fakeRequest)` returns a `Future[Result]`, removing the need for the materializer. – Colin Strong Nov 14 '18 at 16:00