【问题标题】:Mock hackney using Mox使用 Mox 模拟哈克尼
【发布时间】:2021-02-13 02:03:33
【问题描述】:

我正在学习 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 了解行为是什么,我理解错误,问题是我不知道是否有办法为这个模拟“注入”行为定义给哈克尼,或者我使用了错误的工具来完成这项工作。任何想法将不胜感激,在此先感谢:)

【问题讨论】:

  • 我认为为您的客户定义一种行为,将调用 hackney 包装起来是要走的路。然后你可以在你的测试中模拟这种行为。不过,我对此感到很挣扎,因为用您的自定义行为替换所有 http 调用并不完全可行。
  • 我也想过这个,但是项目中散落着一堆hackney的东西,所以我只想模拟一个 :hackney.request 的响应来添加测试而不是打开它进行完整的重构:(
  • 对于 StackOverflow 来说可能有点过于开放 - 试试 elixir 论坛。
  • “项目中散落着一堆乱七八糟的东西”——这已经是一个巨大的架构问题,我将从修复它开始。请参阅 hexdocs.pm/boundary/Boundary.html 了解原因和原因。

标签: mocking elixir


【解决方案1】:

Everett's 的建议可能是您应该遵循的 - 特别是因为 Mox 在 Elixir 中非常受欢迎。

但是,如果您有时间,我建议您查看 Meck,Erlang 的模拟库。它不需要显式的契约/行为,您还可以模拟不存在的函数,这在 TDD 中很有用。使用 Meck,您可以像这样嘲笑 hackney:

setup do
  :ok = :meck.new(:hackney)
  :ok = :meck.expect(:hackney, :get, fn "url" -> :ok end)

  on_exit(fn ->
    :meck.unload(:hackney)
  end)
end

test "mocked hackney request" do
  result = :hackney.get("url")
  assert result == :ok
end

当然,它也支持模拟 Elixir 模块和函数。

【讨论】:

  • 哦,这很有帮助,谢谢,我想我可以为以后制定一个重构并使用 Meck 来发布这个补丁
【解决方案2】:

我将与您分享一种方法,当他们没有定义自己的行为时,我使用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

【讨论】:

  • 谢谢!我不知道。
  • 非常感谢您的详细解释,我将阅读您所写的内容以及@AlekseiMatiushkin 所写的内容,以更好地了解情况并使代码处于更好的状态:)跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-31
  • 1970-01-01
  • 1970-01-01
  • 2022-01-26
  • 1970-01-01
  • 2016-05-24
相关资源
最近更新 更多