根据调用函数的次数返回不同的值

时间:2020-10-27 13:30:06

标签: mocking elixir

背景

我正在一个使用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如何实现我的目标?

2 个答案:

答案 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上下文参数。

相关问题