Невозможно протестировать контроллер с помощью Action.async

Я пытаюсь проверить контроллер, который использует новый Action.async. Следуя documentation, я исключил часть под контроллером. Я хочу проверить отдельный признак с типом ссылки:

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

В документации указано, что я должен проверить ее как:

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 */
      }
    }
  }
}

Для метода index() он работает отлично, к сожалению, я не могу сделать то же самое с register(), так как применение FakeRequest на нем возвращает экземпляр Iteratee[Array[Byte], SimpleResult]. Я заметил, что у него есть метод run(), который возвращает Future[SimpleResult], но независимо от того, как я строю FakeRequest, он возвращается с 400 без какого-либо содержимого или заголовков. Мне кажется, что содержание FakeRequest вообще не учитывается. Должен ли я кормить тело запроса так или иначе, а потом запустить его? Я не мог найти никакого примера, как я мог это сделать.

Ответ 1

Для меня это работает:

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"}"""
    }
  }
}

Ответ 2

Эта проблема возникает из-за того, что play.api.mvc.Action[A] содержит эти два применяемых метода:

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

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

Это возникает из-за того, что Request[A] extends RequestHeader, а A в этом случае делает разницу. Если это не правильный тип, вы в конечном итоге вызываете неправильный apply.

Когда вы используете ActionBuilder с BodyParser[A], вы создаете Action[A]. В результате вам понадобится Request[A] для тестирования. parse.json возвращает a BodyParser[JsValue], поэтому вам нужно Request[JsValue].

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

FakeRequest() не дает вам тип, который вам нужен. К счастью:

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

Итак, начните писать тест, используя заполнитель для тела:

  "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 */
  }