我正在一个使用Mock进行测试的项目中工作。但是我需要运行一个特定的场景,其中函数的输出取决于调用所述函数的次数。我不认为 Mock 支持这一点,所以我试图找到一种方法来进行这个测试。
在这个测试中,我有一个想要模拟的 Storage 模块(它有副作用并且是一个边界)。在测试中,我调用了一个函数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
Run Code Online (Sandbox Code Playgroud)
这里的问题是,由于没有计数器,我无法根据调用函数的调用次数返回不同的输出。
公平地说,Mock 确实提供了一种在输入不同时返回不同输出的方法。然而,这种情况并非如此。输入是一样的,唯一不同的是调用的次数。
如何使用 Mock 实现我的目标?
您可以使用该函数: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
Run Code Online (Sandbox Code Playgroud)
也就是说,不是传递一个匿名函数作为模拟的实现,而是传递一个元组列表,其中每个元组都有一个“参数规范”(在这种情况下[:_, :_],允许任何两个参数)和一个“返回规范”,对于它:meck.seq返回一个魔术值,使模拟每次返回不同的值。