Return 不同的值取决于调用函数的次数
Return different values depending on number of times a function is called
背景
我在一个使用 Mock 进行测试的项目中工作。但是我需要 运行 一个特定的场景,其中函数的输出取决于调用该函数的次数。
我不认为 Mock 支持这个,所以我想找到一种方法来进行这个测试。
代码
在这个测试中,我有一个我想模拟的存储模块(它有副作用并且是一个边界)。
在测试中,我调用了一个函数 get
,第一次是 returns nil
,然后我用 save
保存了一些数据,然后我再次调用了 get。
test_with_mock "returns OK when products list is saved into storage", Storage, [],
[
save: fn _table, data -> {:ok, data} end,
get: fn
_table, _seats_number -> {:ok, nil} # first call it returns nil
_table, _seats_number -> {:ok, [1]} # second call should return some data
end
] do
# Arrange
products = [
%{"id" => 1, "gems" => 4},
%{"id" => 3, "gems" => 4},
]
products_table = :products
# Act & Assert
actual = Engine.save_products(products)
expected = {:ok, :list_saved_successfully}
assert actual == expected
# First call to get returns nil because the table is empty.
# Then we save something into it.
assert_called Storage.get(products_table, 4)
assert_called Storage.save(products_table, {4, [1]})
# Second call should return the product previously saved
# But the mock only returns nil
assert_called Storage.get(products_table, 4)
end
这里的问题是,由于没有计数器,我无法根据调用函数的次数返回不同的输出。
公平地说,当输入不同时,Mock确实提供了一种returns不同输出的方法。然而,这种情况并非如此。输入是一样的,唯一不同的是调用次数。
问题
如何使用 Mock 实现我的目标?
您可以使用函数 :meck.seq
创建一个 return 按顺序给定值的模拟。这依赖于 Mock 只是 meck 之上的薄层这一事实,它允许创建这样的模拟:
test_with_mock "returns OK when products list is saved into storage", Storage, [],
[
save: fn _table, data -> {:ok, data} end,
get: [{[:_, :_], :meck.seq([
{:ok, nil}, # first call it returns nil
{:ok, [1]} # second call should return some data
])}]
] do
也就是说,不是传递一个匿名函数作为模拟的实现,而是传递一个元组列表,其中每个元组都有一个“参数规范”(在本例中 [:_, :_]
,允许任意两个arguments) 和一个“return spec”,其中 :meck.seq
return 是一个神奇的值,它使模拟 return 每次都有不同的值。
您可以使用 agent 来保持可变状态。这是草图:
defmodule MagicTest do
use ExUnit.Case, async: false
import Mock
setup do
{:ok, pid} = Agent.start_link(fn -> 1 end, name: __MODULE__)
{:ok, %{counter: pid}}
end
test_with_mock "test_name", %{counter: counter}, Magic, [],
get: fn -> Agent.get_and_update(counter, fn state -> {state, state + 1} end) end do
assert 1 == Magic.get()
assert 2 == Magic.get()
end
end
defmodule Magic do
def get do
42
end
end
您的测试可能会更复杂一些,但思路是一样的。
模拟自述文件还 briefly explains 如何使用 ExUnit 上下文参数。
背景
我在一个使用 Mock 进行测试的项目中工作。但是我需要 运行 一个特定的场景,其中函数的输出取决于调用该函数的次数。 我不认为 Mock 支持这个,所以我想找到一种方法来进行这个测试。
代码
在这个测试中,我有一个我想模拟的存储模块(它有副作用并且是一个边界)。
在测试中,我调用了一个函数 get
,第一次是 returns nil
,然后我用 save
保存了一些数据,然后我再次调用了 get。
test_with_mock "returns OK when products list is saved into storage", Storage, [],
[
save: fn _table, data -> {:ok, data} end,
get: fn
_table, _seats_number -> {:ok, nil} # first call it returns nil
_table, _seats_number -> {:ok, [1]} # second call should return some data
end
] do
# Arrange
products = [
%{"id" => 1, "gems" => 4},
%{"id" => 3, "gems" => 4},
]
products_table = :products
# Act & Assert
actual = Engine.save_products(products)
expected = {:ok, :list_saved_successfully}
assert actual == expected
# First call to get returns nil because the table is empty.
# Then we save something into it.
assert_called Storage.get(products_table, 4)
assert_called Storage.save(products_table, {4, [1]})
# Second call should return the product previously saved
# But the mock only returns nil
assert_called Storage.get(products_table, 4)
end
这里的问题是,由于没有计数器,我无法根据调用函数的次数返回不同的输出。
公平地说,当输入不同时,Mock确实提供了一种returns不同输出的方法。然而,这种情况并非如此。输入是一样的,唯一不同的是调用次数。
问题
如何使用 Mock 实现我的目标?
您可以使用函数 :meck.seq
创建一个 return 按顺序给定值的模拟。这依赖于 Mock 只是 meck 之上的薄层这一事实,它允许创建这样的模拟:
test_with_mock "returns OK when products list is saved into storage", Storage, [],
[
save: fn _table, data -> {:ok, data} end,
get: [{[:_, :_], :meck.seq([
{:ok, nil}, # first call it returns nil
{:ok, [1]} # second call should return some data
])}]
] do
也就是说,不是传递一个匿名函数作为模拟的实现,而是传递一个元组列表,其中每个元组都有一个“参数规范”(在本例中 [:_, :_]
,允许任意两个arguments) 和一个“return spec”,其中 :meck.seq
return 是一个神奇的值,它使模拟 return 每次都有不同的值。
您可以使用 agent 来保持可变状态。这是草图:
defmodule MagicTest do
use ExUnit.Case, async: false
import Mock
setup do
{:ok, pid} = Agent.start_link(fn -> 1 end, name: __MODULE__)
{:ok, %{counter: pid}}
end
test_with_mock "test_name", %{counter: counter}, Magic, [],
get: fn -> Agent.get_and_update(counter, fn state -> {state, state + 1} end) end do
assert 1 == Magic.get()
assert 2 == Magic.get()
end
end
defmodule Magic do
def get do
42
end
end
您的测试可能会更复杂一些,但思路是一样的。
模拟自述文件还 briefly explains 如何使用 ExUnit 上下文参数。