5

Documentation suggests testing API client based on WSClient using a mock web service, that is, create a play.server.Server which will respond to real HTTP requests.

I would prefer to create WSResponse objects directly from files, complete with status line, header lines and body, without real TCP connections. That would require less dependencies and run faster. Also there may be other cases when this is useful.

But I can't find a simple way to do it. It seems all implementations wrapped by WSResponse are tied to reading from network.

Should I just create my own subclass of WSResponse for this, or maybe I'm wrong and it already exists?

Joan
  • 4,079
  • 2
  • 28
  • 37
Konstantin Pelepelin
  • 1,303
  • 1
  • 14
  • 29
  • You have to used Play WSClient ? You could checkout Resitio Mock framework for testing. I would advice, Developer already creating all this class, take that class as .jar and create mock service out of it. – N.. Jan 12 '16 at 21:11
  • 1
    @BostonStar Yes, I'm bound to WSClient. We use junit+mockito. How will Resito help creating WSResponse objects? – Konstantin Pelepelin Jan 13 '16 at 14:03
  • I think you can direct access original object and create mock service. – N.. Jan 13 '16 at 14:31

3 Answers3

5

The API for Play seems intentionally obtuse. You have to use their "Cacheable" classes, which are the only ones that seem directly instantiable from objects you'd have lying around.

This should get you started:

import play.api.libs.ws.ahc.AhcWSResponse;
import play.api.libs.ws.ahc.cache.CacheableHttpResponseBodyPart;
import play.api.libs.ws.ahc.cache.CacheableHttpResponseHeaders;
import play.api.libs.ws.ahc.cache.CacheableHttpResponseStatus;
import play.shaded.ahc.io.netty.handler.codec.http.DefaultHttpHeaders;
import play.shaded.ahc.org.asynchttpclient.Response;
import play.shaded.ahc.org.asynchttpclient.uri.Uri;

AhcWSResponse response = new AhcWSResponse(new Response.ResponseBuilder()
        .accumulate(new CacheableHttpResponseStatus(Uri.create("uri"), 200, "status text", "protocols!"))
        .accumulate(new CacheableHttpResponseHeaders(false, new DefaultHttpHeaders().add("My-Header", "value")))
        .accumulate(new CacheableHttpResponseBodyPart("my body".getBytes(), true))
        .build());

The mystery boolean values aren't documented. My guess is the boolean for BodyPart is whether that is the last part of the body. My guess for Headers is whether the headers are in the trailer of a message.

Will Beason
  • 3,417
  • 2
  • 28
  • 46
  • 1
    Thanks for the response! Quite indirect solution, but having nothing better built-in, I accept this. – Konstantin Pelepelin Dec 08 '17 at 13:19
  • Yeah, I still haven't gotten it to parse correctly. The cryptic error message hasn't helped, so I factored this out so I can actually test things. – Will Beason Dec 08 '17 at 19:30
  • This did not work for me. For some reason, I cannot find `CacheableHttpResponseHeaders`, usng Play 2.5 – Hawk May 01 '18 at 09:16
  • 1
    I think it's new in 2.6 https://www.playframework.com/documentation/2.6.0-M4/api/scala/index.html#play.api.libs.ws.ahc.cache.CacheableHttpResponseHeaders – Will Beason May 01 '18 at 17:27
3

I used another way, mocking WSResponse with Mockito:

import play.libs.ws.WSRequest;
import play.libs.ws.WSResponse;
import org.mockito.Mockito;

...

          final WSResponse wsResponseMock = Mockito.mock(WSResponse.class);
          Mockito.doReturn(200).when(wsResponseMock).getStatus();
          final String jsonStr = "{\n"
                  + "  \"response\": {\n"
                  + "    \"route\": [\n"
                  + "      { \"summary\" :\n"
                  + "        {\n"
                  + "          \"distance\": 23\n"
                  + "        }\n"
                  + "      }\n"
                  + "    ]\n"
                  + "  }\n"
                  + "}";
          ObjectMapper mapper = new ObjectMapper();
          JsonNode jsonNode = null;
          try {
            jsonNode = mapper.readTree(jsonStr);
          } catch (IOException e) {
            e.printStackTrace();
          }
          Mockito.doReturn(
                  jsonNode)
              .when(wsResponseMock)
              .asJson();
L. G.
  • 9,642
  • 7
  • 56
  • 78
  • 2
    You can use `play.libs.Json#parse(String)` instead of creating an object mapper. Also, it is advisable to use `when(..).thenReturn(..)` instead of `doReturn(..).when(..)`. Upvoted. – Kartik Jun 19 '19 at 01:35
  • The downside of this solution is that one needs to mock all expected calls instead of using external resources or a convenient response building API. – Konstantin Pelepelin Jan 30 '20 at 12:44
3

If you are using play-framework 2.8.x and scala, the below code can help us generate a dummy WSResponse:

import play.api.libs.ws.ahc.AhcWSResponse
import play.api.libs.ws.ahc.cache.CacheableHttpResponseStatus
import play.shaded.ahc.org.asynchttpclient.Response
import play.shaded.ahc.org.asynchttpclient.uri.Uri
import play.api.libs.ws.ahc.cache.CacheableHttpResponseBodyPart
import play.shaded.ahc.io.netty.handler.codec.http.DefaultHttpHeaders

class OutputWriterSpec extends FlatSpec with Matchers {
  val respBuilder = new Response.ResponseBuilder()
  respBuilder.accumulate(new CacheableHttpResponseStatus(Uri.create("http://localhost:9000/api/service"), 202, "status text", "json"))
  respBuilder.accumulate(new DefaultHttpHeaders().add("Content-Type", "application/json"))
  respBuilder.accumulate(new CacheableHttpResponseBodyPart("{\n\"id\":\"job-1\",\n\"lines\": [\n\"62812ce276aa9819a2e272f94124d5a1\",\n\"13ea8b769685089ba2bed4a665a61fde\"\n]\n}".getBytes(), true))
  val resp = new AhcWSResponse(respBuilder.build())

  val outputWriter = OutputWriter

  val expected = ("\"job-1\"", List("\"62812ce276aa9819a2e272f94124d5a1\"", "\"13ea8b769685089ba2bed4a665a61fde\""), "_SUCCESS")
  "Output Writer" should "handle response from api call" in {
    val actual = outputWriter.handleResponse(resp, "job-1")
    println("the actual : " + actual)
    actual shouldEqual(expected)

  }
}
ForeverLearner
  • 1,901
  • 2
  • 28
  • 51