我正在一个使用Mock进行测试的项目中工作。但是我需要运行一个特定的场景,其中函数的输出取决于该函数被调用的次数。 我不认为Mock支持这一点,所以我正在尝试寻找一种进行此测试的方法。
在此测试中,我有一个要模拟的存储模块(它有副作用,是一个边界)。
在测试中,我调用函数get
,该函数第一次返回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
这里的问题是,由于没有计数器,因此我无法根据调用函数的次数返回不同的输出。
公平地说,当 input 不同时,Mock确实提供了一种返回不同输出的方法。然而,这种情况并非如此。输入是相同的,唯一的不同是调用次数。
使用Mock如何实现我的目标?
答案 0 :(得分:4)
您可以使用函数:meck.seq
创建一个模拟,该模拟将按顺序返回给定的值。这依赖于事实,即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
也就是说,不要传递匿名函数作为模拟的实现,而是传递一个元组列表,其中每个元组都有一个“参数规范”(在本例中为[:_, :_]
,以允许任何两个参数)和“返回规格”,:meck.seq
返回一个魔术值,使模拟每次返回不同的值。
答案 1 :(得分:2)
您可以使用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上下文参数。