【问题标题】:Mocking of BlazeClientBuilder[IO] to return mock client[IO]模拟 BlazeClientBuilder[IO] 以返回模拟客户端 [IO]
【发布时间】:2019-07-02 10:34:22
【问题描述】:

我正在使用BlazeClientBuilder[IO].resource 方法获取Client[IO]。现在,我想模拟客户端进行单元测试,但不知道该怎么做。有没有一种很好的方法来模拟这个,我该怎么做?

class ExternalCall(val resource: Resource[IO, Client[IO]], externalServiceUrl: Uri) {
def retrieveData: IO[Either[Throwable, String]] = {
for {
  req <- IO(Request[IO](Method.GET, uri = externalServiceUrl))
  response <- resource.use(client => {
    client.fetch[String](req)(httpResponse => {
      if (!httpResponse.status.isSuccess)
        throw new Exception(httpResponse.status.reason)
      else
        httpResponse.as[String]
    })
  })
} yield Right(response)
}
}

来电代码

new ExternalCall(BlazeClientBuilder[IO](global).resource).retrieveData

【问题讨论】:

  • 您究竟为什么要这样做?模拟本身不应该是一个目标——它是一种旨在帮助隔离某些组件以进行测试的工具。有可能(甚至可能)有更好的方法来编写这些测试。
  • 我正在使用http4sclient使用builder获取外部数据,现在我想模拟外部数据以测试各种场景。
  • 你能提供一个关于你如何注入和使用BlazeClientBuilder[IO].resource的代码的sn-p吗?
  • 添加代码sn-p
  • 你将如何存根(而不是模拟)为任何请求返回硬编码响应?

标签: scala mockito scala-cats io-monad


【解决方案1】:

您可以使用以下 sn-p 轻松模拟客户端

import fs2.Stream
import org.http4s.Response
import org.http4s.client.Client

def httpClient(body: String): Client[IO] = Client.apply[IO] { _ =>
    Resource.liftF(IO(Response[IO](body = Stream.emits(body.getBytes("UTF-8")))))
}

为了将客户端作为资源,您需要用IO 包装它并提升到Resource

Resource.liftF(IO(httpClient("body")))

【讨论】:

    【解决方案2】:

    看来你只需要做类似的事情

    val resourceMock = mock[Resource[IO, Client[IO]]]
    //stub whatever is necessary
    val call = new ExternalCall(resourceMock).retrieveData
    //do asserts and verifications as needed
    

    编辑:

    您可以在下面看到一个完整的示例,但我想强调,这是一个很好的示例,说明了为什么避免模拟您不拥有的 API 是一种很好的做法。

    更好的测试方法是将与 http4s 相关的代码放在您拥有的类(YourHttpClient 或其他)中,并为该类编写一个集成测试,检查 http4s 客户端是否正确(您可以使用wiremock来模拟一个真实的http服务器)。

    然后你可以将 YourHttpClient 的模拟传递给依赖它的组件,优势在于你可以控制它的 API,这样它会更简单,如果 http4s 更新它的 API,你只有一个破坏类,而不必修复数十或数百个模拟交互。

    顺便说一句,该示例是使用 mockito-scala 编写的,因为使用 Java 版本的 mockito 会产生更难阅读的代码。

        val resourceMock = mock[Resource[IO, Client[IO]]]
        val clientMock   = mock[Client[IO]]
        val response: Response[IO] = Response(Status.Ok,
                                              body = Stream("Mocked!!!").through(text.utf8Encode),
                                              headers = Headers(`Content-Type`(MediaType.text.plain, Charset.`UTF-8`)))
    
        clientMock.fetch[String](any[Request[IO]])(*) shouldAnswer { (_: Request[IO], f: Response[IO] => IO[String]) =>
          f(response)
        }
    
        resourceMock.use[String](*)(*) shouldAnswer { (f: Client[IO] => IO[String]) =>
          f(clientMock)
        }
    
        val data = new ExternalCall(resourceMock, Uri.unsafeFromString("http://www.example.com")).retrieveData
    
        data.unsafeRunSync().right.value shouldBe "Mocked!!!"
    

    【讨论】:

    • 如何模拟 resource.use?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-01
    • 2015-11-10
    • 2014-08-10
    • 1970-01-01
    • 2019-06-22
    相关资源
    最近更新 更多