Unable to test controller using Action.async
Asked Answered
C

2

13

I'm trying to test controller, which is using new Action.async. Following documentation I have excluded part under controller I want to test to separate trait with type reference:

trait UserController { this: Controller =>
  def index() = Action { /* snip */ }
  def register() = Action.async(parse.json) { request => /* snip */ }
}

Documentation states that I'm supposed to test it as:

object UsersControllerSpec extends PlaySpecification with Results {
  class TestController() extends Controller with UserController
    "index action" should {
      "should be valid" in {
        val controller = new TestController()
        val result: Future[SimpleResult] = controller.index().apply(FakeRequest())
        /* assertions */
      }
    }
  }
}

For index() method it works perfectly, unfortunately I'm not able to do the same with register(), as applying FakeRequest on it returns instance of Iteratee[Array[Byte], SimpleResult]. I've noticed it has run() method that returns Future[SimpleResult] but no matter how I build FakeRequest it returns with 400 without any content or headers. Seems to me like content of FakeRequest is disregarded at all. Am I supposed to feed request body to iteratee somehow and then run it? I couldn't find any example how could I do that.

Caithness answered 18/10, 2013 at 14:43 Comment(1)
Any luck with this? It's driving me crazy!Abstraction
G
7

For me works this:

import concurrent._
import play.api.libs.json._
import play.api.mvc.{SimpleResult, Results, Controller, Action}
import play.api.test._
import ExecutionContext.Implicits.global

trait UserController {
  this: Controller =>
  def index() = Action {
    Ok("index")
  }

  def register() = Action.async(parse.json) {
    request =>
      future(Ok("register: " + request.body))
  }
}

object UsersControllerSpec extends PlaySpecification with Results {

  class TestController() extends Controller with UserController

  "register action" should {
    "should be valid" in {
      val controller = new TestController()
      val request = FakeRequest().withBody(Json.obj("a" -> JsString("A"), "b" -> JsString("B")))
      val result: Future[SimpleResult] = controller.register()(request)
      /* assertions */
      contentAsString(result) === """register: {"a":"A","b":"B"}"""
    }
  }
}
Ganesa answered 18/10, 2013 at 18:15 Comment(2)
I'm getting similar problems for my secured actions, is there a similar solution? #26304205Dissociable
A gotcha on this, and further to @tjdett's comments, is that withJsonBody returns a FakeRequest[AnyContentAsJson], not FakeRequest[JsValue]. So don't use withJsonBody, use withBody.Baize
P
10

This problem arises because play.api.mvc.Action[A] contains these two apply methods:

// What you're hoping for
def apply(request: Request[A]): Future[Result]

// What actually gets called
def apply(rh: RequestHeader): Iteratee[Array[Byte], Result]

This arises because Request[A] extends RequestHeader, and the A in this case makes all the difference. If it's not the right type, you'll end up calling the wrong apply.

When you use ActionBuilder with a BodyParser[A], you create an Action[A]. As a result, you'll need a Request[A] to test. parse.json returns a BodyParser[JsValue], so you need a Request[JsValue].

// In FakeRequest object
def apply(): FakeRequest[AnyContentAsEmpty.type]

FakeRequest() doesn't get you the type you need. Fortunately:

// In FakeRequest class
def withBody[B](body: B): FakeRequest[B]

So, start writing your test by using a placeholder for the body:

  "should be valid" in {
    val controller = new TestController()
    val body: JsValue = ??? // Change this once your test compiles

    // Could do these lines together, but this shows type signatures
    val request: Request[JsValue] = FakeRequest().withBody(body)
    val result: Future[Result] = controller.index().apply(request)

    /* assertions */
  }
Pneumococcus answered 2/9, 2014 at 4:44 Comment(0)
G
7

For me works this:

import concurrent._
import play.api.libs.json._
import play.api.mvc.{SimpleResult, Results, Controller, Action}
import play.api.test._
import ExecutionContext.Implicits.global

trait UserController {
  this: Controller =>
  def index() = Action {
    Ok("index")
  }

  def register() = Action.async(parse.json) {
    request =>
      future(Ok("register: " + request.body))
  }
}

object UsersControllerSpec extends PlaySpecification with Results {

  class TestController() extends Controller with UserController

  "register action" should {
    "should be valid" in {
      val controller = new TestController()
      val request = FakeRequest().withBody(Json.obj("a" -> JsString("A"), "b" -> JsString("B")))
      val result: Future[SimpleResult] = controller.register()(request)
      /* assertions */
      contentAsString(result) === """register: {"a":"A","b":"B"}"""
    }
  }
}
Ganesa answered 18/10, 2013 at 18:15 Comment(2)
I'm getting similar problems for my secured actions, is there a similar solution? #26304205Dissociable
A gotcha on this, and further to @tjdett's comments, is that withJsonBody returns a FakeRequest[AnyContentAsJson], not FakeRequest[JsValue]. So don't use withJsonBody, use withBody.Baize

© 2022 - 2024 — McMap. All rights reserved.