我有这样的功能
def foo_bar() do
Enum.reduce_while(
image_options,
0,
fn image_option, _foo ->
case image_option["destination"] do
"s3" ->
case response = Upload.upload_on_s3(foo, bar) do
{:ok, _} ->
{:cont, {:ok, "ok"}}
{:error, _} ->
{:halt, response}
end
_ ->
{:cont, {:ok, "todo"}}
end
end
)
end
我想在单元测试中测试foo_bar。我如何模仿Upload.upload_on_s3(foo, bar)
函数?
答案 0 :(得分:4)
您可以更改foo_bar
以接受相关性。下面我展示了一个带有默认参数的模块,但你可以省略默认值,或者如果你愿意,可以传递一个函数:
def foo_bar(upload_module \\ Upload) do
Enum.reduce_while(
image_options,
0,
fn image_option, _foo ->
case image_option["destination"] do
"s3" ->
case response = upload_module.upload_on_s3(foo, bar) do
{:ok, _} ->
{:cont, {:ok, "ok"}}
{:error, _} ->
{:halt, response}
end
_ ->
{:cont, {:ok, "todo"}}
end
end
)
end
然后,在您的单元测试中,您可以传递您自己的虚假版本的上传模块,以获得您想要的行为。例如:
defmodule BadFakeUploader do
def upload_on_s3(_foo, _bar) do
{:error, "bad stuff"}
end
end
defmodule TestFooBar do
use ExUnit.Case
test "does the expected thing" do
assert whatever == SUT.foo_bar(BadFakeUploader)
end
end
答案 1 :(得分:2)
@trptcolin写了完全有效的答案,但是明确地接受了upload_module作为param对我来说它有点黑客,因为你通过注入模拟故意影响工作应用程序的行为。
我总是遇到这样的情况:
1. Create a config for such case
# config.exs
config :my_app, :uploader,
RealUploader
# test.exs
config :my_app, :uploader,
MockUploader
2. Write a mock uploader
# mock only public functions
3. Use it as module attribute to don't change the function call.
@uploader Application.get_env(:my_app, :uploader)
# few lines below...
@uploader.upload_on_s3(foo, bar)
这只是一种风格问题,但我的建议不是改变函数签名及其参数列表,只是因为你想模仿依赖。使用config的另一个好处是,您可以将所有外部依赖项放在一个位置列出。对于项目中的新人来说,这将更加明确。
答案 2 :(得分:0)
我会使用MecksUnit(我写的一个Hex包),因为我反对为了嘲笑而修改(“暴露”)代码。
与Mock相对,它确实支持异步测试(因为模拟模块是隔离的),并且定义模拟模块更加可读/优雅。
尽管MecksUnit使用:meck
(如果您想尽可能地不引人注目,这是不可避免的),但它通过对每个模块-功能-arity组合仅模拟一次来尝试“尽可能经济”。
取自https://github.com/archan937/mecks_unit/blob/master/test/mecks_unit/bar_test.exs的示例:
defmodule MecksUnit.BarTest do
use ExUnit.Case, async: true
use MecksUnit.Case
defmock List do
def wrap(:bar_test), do: ~w(MecksUnit Bar Test)
end
setup do
{:ok, %{conn: "<conn>"}}
end
mocked_test "parallel compiling", %{conn: conn} do
task =
Task.async(fn ->
assert "<conn>" = conn
assert [:foo, :bar] == List.wrap([:foo, :bar])
assert ~w(MecksUnit Bar Test) == List.wrap(:bar_test)
assert called(List.wrap(:bar_test))
end)
Task.await(task)
end
end