Playframework: Einfacher Controller mit JSON Body kann nicht getestet werden

Erstellt am 3. Okt. 2017  ·  4Kommentare  ·  Quelle: playframework/playframework

Suchen Sie Hilfe?

Nein

Spielversion

2.6.5

API

Skala

Betriebssystem

Mac OS:

Darwin ***.local 17.0.0 Darwin Kernel Version 17.0.0: Thu Aug 24 21:48:19 PDT 2017; root:xnu-4570.1.46~2/RELEASE_X86_64 x86_64

JDK (Oracle 1.8.0_72, OpenJDK 1.8.x, Azul Zing)

java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

Bibliotheksabhängigkeiten

scalaVersion := "2.12.3"
"org.scalatestplus.play" %% "scalatestplus-play" % "3.1.1"

Erwartetes Verhalten

  1. Der Status der getesteten Controller-Methode wird zurückgegeben

Tatsächliches Verhalten

  1. 400-Status wird zurückgegeben
  2. Die folgende Fehlermeldung wird im Antworttext zurückgegeben

Ungültige Anforderung
Für Anfrage 'GET /' [Invalid Json: No content to map due to end of input at [Source: akka.util. ByteIterator$ByteArrayIterator$$anon$1@161d95c6; Zeile: 1, Spalte: 0]]

Reproduzierbarer Testfall

Bitte suchen Sie das folgende Repository, um das Problem zu reproduzieren:
https://github.com/Abrasha/playframework-issue-example

Hilfreichster Kommentar

Hallo @Abrasha , wenn Sie Ihrem Test Typanmerkungen hinzufügen, können Sie sehen, was passiert:

val request: Request[AnyContentAsJson] = FakeRequest()
  .withJsonBody(Json.toJson(person))
  .withHeaders(CONTENT_TYPE -> JSON)
val acceptAction: Action[JsValue] = homeController.accept
val response: Accumulator[ByteString, Result] = acceptAction.apply(request)
assert(status(response) === CREATED)

Das Problem ist, dass acceptAction.apply überladen ist – Sie können entweder ein RequestHeader oder ein Request[JsValue] übergeben. Da Sie versuchen, ein Request[AnyContentAsJson] zu übergeben, entspricht dies der RequestHeader -Version. Dies gibt Accumulator zurück.

Wenn Sie homeController.accept.apply aufrufen, erhalten Sie kein Ergebnis zurück, sondern Accumulator[ByteString, Result] . Wenn Sie status(response) aufrufen, wird dem Akkumulator ein leeres ByteString zugeführt, was zu einem Parsing-Fehler führt.

Die Lösung besteht darin, ein Request[JsValue] zu erstellen, das dem Typ von acceptAction entspricht. Wenn Sie Request[JsValue] verwenden, ruft es die richtige Methode accept auf und gibt Future[Result] zurück

val request: Request[AnyContentAsJson] = FakeRequest()
  .withBody(Json.toJson(person)) // <--- change here
  .withHeaders(CONTENT_TYPE -> JSON)
val acceptAction: Action[JsValue] = homeController.accept
val response: Future[Result] = acceptAction.apply(request) // <-- different type here
assert(status(response) === CREATED)

Die alternative Lösung besteht darin, ein AnyContent BodyParser zu verwenden – was sowieso der Standard ist. Siehe https://www.playframework.com/documentation/2.6.x/ScalaTestingWithScalaTest#Unit -Testing-EssentialAction für ein Beispiel, wie dies funktioniert. Sie müssen request.body.asJson.get in Ihrer Aktion verwenden, um das Ergebnis zu erhalten.

Übrigens ist mir klar, dass dies sehr verwirrend ist, aber leider ist es zu schwierig, Play zu ändern, um es weniger verwirrend zu machen. Hier kommen mehrere Designfragen zusammen:

  • Request verlängert RequestHeader - das verursacht überall Probleme, aber wir können es jetzt nicht ändern
  • Action überlädt die Methode apply für alles, anstatt Methoden mit separaten Namen zu haben, zB accumulator(rh: RequestHeader) und invoke(r: Request[T])
  • AnyContent sollte nur selten verwendet werden, aber es ist die Standardeinstellung in unserem Code und unserer Dokumentation
  • Action ist auch ein ActionBuilder – wiederum unter Verwendung apply -Methoden – und macht es verwirrend zu verstehen, wie Action s aufgebaut sind

Ich werde dieses Ticket jetzt schließen, da es wie vorgesehen funktioniert und wir das Design leider nicht ändern können!

Alle 4 Kommentare

Danke für den Bericht. Ich werde mir das jetzt mal anschauen.

Hallo @Abrasha , wenn Sie Ihrem Test Typanmerkungen hinzufügen, können Sie sehen, was passiert:

val request: Request[AnyContentAsJson] = FakeRequest()
  .withJsonBody(Json.toJson(person))
  .withHeaders(CONTENT_TYPE -> JSON)
val acceptAction: Action[JsValue] = homeController.accept
val response: Accumulator[ByteString, Result] = acceptAction.apply(request)
assert(status(response) === CREATED)

Das Problem ist, dass acceptAction.apply überladen ist – Sie können entweder ein RequestHeader oder ein Request[JsValue] übergeben. Da Sie versuchen, ein Request[AnyContentAsJson] zu übergeben, entspricht dies der RequestHeader -Version. Dies gibt Accumulator zurück.

Wenn Sie homeController.accept.apply aufrufen, erhalten Sie kein Ergebnis zurück, sondern Accumulator[ByteString, Result] . Wenn Sie status(response) aufrufen, wird dem Akkumulator ein leeres ByteString zugeführt, was zu einem Parsing-Fehler führt.

Die Lösung besteht darin, ein Request[JsValue] zu erstellen, das dem Typ von acceptAction entspricht. Wenn Sie Request[JsValue] verwenden, ruft es die richtige Methode accept auf und gibt Future[Result] zurück

val request: Request[AnyContentAsJson] = FakeRequest()
  .withBody(Json.toJson(person)) // <--- change here
  .withHeaders(CONTENT_TYPE -> JSON)
val acceptAction: Action[JsValue] = homeController.accept
val response: Future[Result] = acceptAction.apply(request) // <-- different type here
assert(status(response) === CREATED)

Die alternative Lösung besteht darin, ein AnyContent BodyParser zu verwenden – was sowieso der Standard ist. Siehe https://www.playframework.com/documentation/2.6.x/ScalaTestingWithScalaTest#Unit -Testing-EssentialAction für ein Beispiel, wie dies funktioniert. Sie müssen request.body.asJson.get in Ihrer Aktion verwenden, um das Ergebnis zu erhalten.

Übrigens ist mir klar, dass dies sehr verwirrend ist, aber leider ist es zu schwierig, Play zu ändern, um es weniger verwirrend zu machen. Hier kommen mehrere Designfragen zusammen:

  • Request verlängert RequestHeader - das verursacht überall Probleme, aber wir können es jetzt nicht ändern
  • Action überlädt die Methode apply für alles, anstatt Methoden mit separaten Namen zu haben, zB accumulator(rh: RequestHeader) und invoke(r: Request[T])
  • AnyContent sollte nur selten verwendet werden, aber es ist die Standardeinstellung in unserem Code und unserer Dokumentation
  • Action ist auch ein ActionBuilder – wiederum unter Verwendung apply -Methoden – und macht es verwirrend zu verstehen, wie Action s aufgebaut sind

Ich werde dieses Ticket jetzt schließen, da es wie vorgesehen funktioniert und wir das Design leider nicht ändern können!

@richdougherty Danke für deine Erklärung, du sparst mir viel Zeit :)

Das ist einer meiner Lieblingshasser an Play, die überladende Semantik ist verrückt und schlecht dokumentiert.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen