首页 > 解决方案 > 使用 Mox 模拟哈克尼

问题描述

我正在学习 Elixir 和 Phoenix 堆栈,然后我进入了一个使用 Hackney 模块 (erlang) 进行 http 调用的生产项目。这里的情况是我想模拟 hackney 的“请求”函数,所以当它被调用时,它会返回一个特定的响应。到目前为止,我已经四处奔波并登陆 Mox 库来实现这一点,但是由于 hackney 是一个 Erlang 模块,而 Mox 抱怨它不是一种行为,我想知道是否有更好的方法来做到这一点或者也许还有另一个缺少的配置。代码失败的地方是在模拟定义的开头:

Mox.defmock(MyApp.MockHackney, for: :hackney) 这只是失败了module :hackney is not a behaviour, please pass a behaviour to :for

我假设我做错了什么(我在这里关注 Mox 库的文档:https ://hexdocs.pm/mox/Mox.html 了解行为是什么,我了解错误,事情是我不知道是否有办法为这个模拟“注入”行为定义给哈克尼,或者我使用了错误的工具来完成这项工作。任何想法都将不胜感激,在此先感谢 :)

标签: mockingelixir

解决方案


我将与您分享一种我用来模拟各种 3rd 方库的方法Mox,当它们没有定义自己的行为时。

首先:定义您自己的 Elixir 行为,该行为定义与您从该库中使用的函数相对应的回调。例如,如果您正在调用,:hackney.get/2则在您的行为中为该函数定义一个回调。定义一个您可能实际上并未在代码中使用的行为可能看起来毫无意义,但它为您做了两件重要的事情:

  1. 它可以帮助您记录您正在使用的给定模块中的哪些功能,因此它是对该功能进行抽象的第一步(想想如果您突然不得不更换使用的 HTTP 客户端库,您可能需要如何重构事物)。
  2. 它满足了 Mox 对具有可以通过反射检查的行为的需求。

例如

defmodule MyClientBehaviour do
  @callback get(url :: binary()) :: {:ok, any()} | {:error, any()}
end

test/请注意,如果它确实仅用于测试,您可以在目录中定义行为。

第二:调整您想要测试的代码,以便您可以在运行时提供模块。有两种常见的方法可以做到这一点:

  1. 通过提供的opt, 或
  2. 从配置中解析模块。

例如,如果您的代码:hackney用于拨打电话,例如

def get(url, opts \\ []) do
    :hackney.get(url)
end

您可以改为提供覆盖选项,例如

def get(url, opts \\ []) do
    client = Keyword.get(opts, :client, :hackney)
    client.get(url)
end

这样,您可以保留默认实现(:hackney在您的情况下),现在可以在测试期间传入模拟。通常,您test_helper.exs将声明正在使用的模拟,例如

# test_helper.exs
ExUnit.start()

Mox.Server.start_link([])

Mox.defmock(HTTPClientMock, for: MyClientBehaviour)
defmodule YourTest do
   use ExUnit.Case

  import Mox
  
  setup :verify_on_exit!

  test ":ok something" do
      client =
        HTTPClientMock
        |> expect(:get, fn _ ->
          {:ok, "some response here"}
        end)
    
       assert {:ok, _} = YourModule.get("http://example.com", client: client)
  end
end

如果您调用:hackey的位置嵌入得太深而无法进行这种opts注入,则将其定义为可配置模块会很有用,例如

def get(url, opts \\ []) do
    client = Application.get_env(:my_app, :http_client, :hackney)
    client.get(url)
end

在这种情况下,您可以在运行测试之前将值放入应用程序配置中(或者您可以将其添加到test.exs配置中)。这最好在一个setup块中完成,以确保它在测试之前完成,并确保在完成后将其设置回来。

setup do
    http_client = Application.get_env(:my_app, :http_client)

    on_exit(fn ->
      Application.put_env(:my_app, :http_client, http_client)
    end)
end

推荐阅读