无法使用scalamock模拟WSRequest.post()

时间:2018-01-28 16:54:11

标签: scala playframework scalamock play-ws

我正在使用Scalamock和Scalatest为Play应用程序编写单元测试。

我的原始代码如下:

// Here ws is an injected WSClient
val req = Json.toJson(someRequestObject)
val resp: Future[WSResponse] = ws.url(remoteURL).post(Json.toJson(req))

在一个部分中,我必须模拟对Web服务的外部调用,我正在尝试使用scalamock:

ws = stub[WSClient]
wsReq = stub[WSRequest]
wsResp = stub[WSResponse]

ws.url _ when(*) returns wsReq
wsReq.withRequestTimeout _ when(*) returns wsReq
(wsReq.post (_: java.io.File)).when(*) returns Future(wsResp)

我已成功使用文件模拟发布请求,但我无法使用JSON模拟发布请求。

我尝试单独设置存根函数引用,如:

val f: StubFunction1[java.io.File, Future[WSResponse]] = wsReq.post (_: java.io.File)

val j: StubFunction1[JsValue, Future[WSResponse]] = wsReq.post(_: JsValue)

我收到第二行的编译错误:Unable to resolve overloaded method post

我在这里缺少什么?为什么我不能模拟一个重载方法而不能模拟另一个?

4 个答案:

答案 0 :(得分:1)

play.api.libs.ws.WSRequest有两个post方法(https://www.playframework.com/documentation/2.4.x/api/scala/index.html#play.api.libs.ws.WSRequest),其中包含:

  1. File
  2. T(其中TWriteable上有隐含边界)
  3. 编译器失败是因为您尝试使用单个参数调用post,该参数仅匹配版本1.但JsValue不能替换为File

    你实际上想要调用第二个版本,但这是一个带有两组参数的curried方法(尽管第二个是隐式的)。因此,您需要明确提供您期望的隐式模拟值,即

    val j: StubFunction1[JsValue, Future[WSResponse]] = wsReq.post(_: JsValue)(implicitly[Writeable[JsValue]])

    因此,一个有效的解决方案是:

    (wsReq.post(_)(_)).when(*) returns Future(wsResp)

    旧答案:

    WSRequest提供了post方法(https://www.playframework.com/documentation/2.5.8/api/java/play/libs/ws/WSRequest.html)的4次重载,其中包含:

    1. String
    2. JsonNode
    3. InputStream
    4. File
    5. 您可以使用File进行模拟,因为它匹配重载4,但JsValue不匹配(这是Play JSON模型的一部分,而JsonNode是Jackson JSON的一部分模型)。如果您转换为StringJsonNode,那么它将解析正确的重载并进行编译。

答案 1 :(得分:1)

我最好的猜测是,您的WSRequest实际上是play.libs.ws.WSRequest,它是Java API的一部分,而您应该使用play.api.libs.ws.WSRequest这是Scala API。

方法WSRequest.post存在且BodyWritable[JsValue]由Scala API中的WSBodyWritables隐式提供,但未在Java API中提供。

另一个原因可能是您的JsValue不是play.api.libs.json.JsValue而是其他内容(例如spray.json.JsValue)。

答案 2 :(得分:1)

我将举例说明我已成功实现您的目标,主要区别在于我使用mock代替stub

重要的部分是:

val ws = mock[WSClient]
val responseBody = "{...}"
...
"availableBooks" should {
  "retrieve available books" in {
    val expectedBooks = "BTC_DASH ETH_DASH USDT_LTC BNB_LTC".split(" ").map(Book.fromString).map(_.get).toList

    val request = mock[WSRequest]
    val response = mock[WSResponse]
    val json = Json.parse(responseBody)

    when(ws.url(anyString)).thenReturn(request)
    when(response.status).thenReturn(200)
    when(response.json).thenReturn(json)
    when(request.get()).thenReturn(Future.successful(response))

    whenReady(service.availableBooks()) { books =>
      books.size mustEqual expectedBooks.size

      books.sortBy(_.string) mustEqual expectedBooks.sortBy(_.string)
    }
  }
}

你可以在BinanceServiceSpec

看到完整的测试

答案 3 :(得分:0)

如果您模拟的是JsValue响应,我想它应该可以正常工作。

when(wsReq.post(Json.parse("""{...json request...}"""))).thenReturn(Future(wsResp))

此处Json.parse返回JsValue。您应该在请求正文中传递所需的json字符串。