Return different values depending on number of times a function is called

Background

I am working in a project that uses Mock for testing. However I need to run a specific scenario, where the output of a function depends on the number of times said function is called.
I don’t think Mock supports this, so I am trying to find out a way to conduct this test.

Code

In this test I have a Storage module which I want to mock (it has side effects and is a boundary).
In the test, I call a function get, which the first time returns nil, then I save some data with save and then I call get again.

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

The issue here is that since there is no counter, I don’t have a way of returning a different output depending on the number of calls a function was called.

To be fair, Mock does offer a way to returns different outputs when the input is different. However, this is not the case. The input is the same, the only different is the number of invocations.

Question

How can I achieve my goal using Mock?

You could use the process dict for storing how many times the function was called yet. Given Mock doesn’t support async tests anyways you shouldn’t get into trouble with that.

2 Likes

I think for what you’ve describe the process dictionary is a good way to go

Something I’ve done in the past is send messages to self() containing what I want the mock function to return.

So in my test I would do something like this:

send self(), {:mock_return, {:ok, nil}}
send self(), {:mock_return, {:ok, [1]}}

And then my mock function would look something like this:

fn _table, _seats_number ->
  receive do
    {:mock_return, value} ->
      value
  after
    0 ->
      raise "called too many times"
  end
end

I often do this without a mocking library and send messages back, using assert_received/2 in place of assert_called/1 or the like. It also lets me use refs instead of :mock_return.

2 Likes