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

Cannot mock WSRequest.post() using scalamock

我正在使用 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)

我可以使用文件成功模拟 post 请求,但我无法使用 JSON.

模拟 post 请求

我尝试将存根函数引用分开放置,例如:

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

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

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 上有一个隐式边界)

编译器失败,因为您正尝试使用单个参数调用 post,该参数仅匹配版本 1。但是,JsValue 不能替换为 File

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

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

因此,一个可行的解决方案是:

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

旧答案:

WSRequest提供了4个重载post方法(https://www.playframework.com/documentation/2.5.8/api/java/play/libs/ws/WSRequest.html),取:

  1. String
  2. JsonNode
  3. InputStream
  4. File

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

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

方法 WSRequest.post exists and BodyWritable[JsValue] 在 Scala API 中由 WSBodyWritables 隐式提供,但在 Java API.

中不提供

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

我会引用一个例子,我已经成功地实现了你想要做的事情,主要区别是我使用 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

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

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

此处Json.parsereturnsJsValue。你应该在请求正文中传递你期望的 json 字符串。