使用 Mox 模拟 hackney
Mock hackney using Mox
我正在学习 Elixir 和 Phoenix 堆栈,我被投入到一个使用 Hackney 模块 (erlang) 进行 http 调用的生产项目中。这里的情况是我想模拟 hackney 的“请求”函数,所以当它被调用时,它 returns 一个特定的响应。到目前为止,我已经 运行 找到了 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
理解行为是什么,我理解错误,问题是我不知道是否有办法为这个模拟将行为定义“注入”到哈克尼,或者我使用了错误的工具来完成这项工作。任何想法将不胜感激,提前致谢:)
我将与您分享我使用 Mox
模拟各种第 3 方库的方法,当它们没有定义自己的行为时。
首先: 定义您自己的 Elixir 行为,该行为定义与您正在使用的该库中的函数相对应的回调。例如,如果您正在调用 :hackney.get/2
,则在您的行为中为该函数定义一个回调。定义一个你可能不会在你的代码中实际使用的行为可能看起来毫无意义,但它为你做了两件重要的事情:
- 它可以帮助您记录您正在使用给定模块中的哪些功能,因此这是对该功能进行抽象的第一步(想想如果您突然不得不换掉 HTTP,您可能需要如何重构使用的客户端库)。
- 它满足了 Mox 对具有可以通过反射检查的行为的需求。
例如
defmodule MyClientBehaviour do
@callback get(url :: binary()) :: {:ok, any()} | {:error, any()}
end
请注意,如果确实仅用于测试,您可以在 test/
目录中定义行为。
其次:调整您要测试的代码,以便您可以在运行时提供模块。有两种常见的方法可以做到这一点:
- 通过提供的
opt
或 使值可覆盖
- 从配置中解析模块。
例如,如果您的代码使用 :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
Everett's 建议可能是您应该遵循的 - 特别是因为 Mox 在 Elixir 中非常受欢迎。
但是,如果您有时间,我建议您查看 Meck,Erlang 的模拟库。它不需要显式 contracts/behaviours,您还可以模拟不存在的函数,这在 TDD 中很有用。有了梅克,你会像这样嘲笑哈克尼:
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 模块和函数。
我正在学习 Elixir 和 Phoenix 堆栈,我被投入到一个使用 Hackney 模块 (erlang) 进行 http 调用的生产项目中。这里的情况是我想模拟 hackney 的“请求”函数,所以当它被调用时,它 returns 一个特定的响应。到目前为止,我已经 运行 找到了 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 理解行为是什么,我理解错误,问题是我不知道是否有办法为这个模拟将行为定义“注入”到哈克尼,或者我使用了错误的工具来完成这项工作。任何想法将不胜感激,提前致谢:)
我将与您分享我使用 Mox
模拟各种第 3 方库的方法,当它们没有定义自己的行为时。
首先: 定义您自己的 Elixir 行为,该行为定义与您正在使用的该库中的函数相对应的回调。例如,如果您正在调用 :hackney.get/2
,则在您的行为中为该函数定义一个回调。定义一个你可能不会在你的代码中实际使用的行为可能看起来毫无意义,但它为你做了两件重要的事情:
- 它可以帮助您记录您正在使用给定模块中的哪些功能,因此这是对该功能进行抽象的第一步(想想如果您突然不得不换掉 HTTP,您可能需要如何重构使用的客户端库)。
- 它满足了 Mox 对具有可以通过反射检查的行为的需求。
例如
defmodule MyClientBehaviour do
@callback get(url :: binary()) :: {:ok, any()} | {:error, any()}
end
请注意,如果确实仅用于测试,您可以在 test/
目录中定义行为。
其次:调整您要测试的代码,以便您可以在运行时提供模块。有两种常见的方法可以做到这一点:
- 通过提供的
opt
或 使值可覆盖
- 从配置中解析模块。
例如,如果您的代码使用 :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
Everett's 建议可能是您应该遵循的 - 特别是因为 Mox 在 Elixir 中非常受欢迎。
但是,如果您有时间,我建议您查看 Meck,Erlang 的模拟库。它不需要显式 contracts/behaviours,您还可以模拟不存在的函数,这在 TDD 中很有用。有了梅克,你会像这样嘲笑哈克尼:
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 模块和函数。