Borys Zielonka
Borys Zielonka

Reputation: 297

Mocking consecutive function calls in Elixir with Mock or Mox

I'm trying to mock function multiple calls, so it returns every time specific different value. I am not that familiar with Elixir and functional concepts.

defmodule Roller do
  def roll do
      1..10
      |>Enum.random()
  end
end

Roller returns a random number every call.

defmodule VolunteerFinder do
  import Roller

  def find(list) do
    find(list, []);
  end

  defp find([] = _list, result) do
    result
  end

  defp find([head | tail] = _list, result) do
    result = [%{name: head, score: Roller.roll()} | result]
    find(tail, result)
  end
end

So assuming the list contains more than one element, the roller is called 2 times. In my test, I need to control it somehow.

I tried it with Mock. I would like to do something like this in the simplest possible way. It would be great not to have to save some state anywhere or run separate processes for every call. I know Elixir thinking might be a little different than the objective paradigm mindset I have. What's the most correct Elixir way of testing the VolunteerFinder module?

defmodule VolunteerFinderTest do
  use ExUnit.Case

  import Mock
  import Roller

  test_with_mock(
    "Find volunteer for list with one element",
    Roller,
    [roll: fn() -> 5 end]
  ) do
    assert VolunteerFinder.find(["John"]) == [%{name: "John", score: 5}]
  end


  test_with_mock(
    "Find volunteer for list with two elements",
    Roller,
    [roll: fn
       () -> 2
       () -> 5
    end]
  ) do
    assert VolunteerFinder.find(["John", "Andrew"])
    == [%{name: "Andrew", score: 5}, %{name: "John", score: 2}]
  end
end

Upvotes: 1

Views: 804

Answers (1)

Borys Zielonka
Borys Zielonka

Reputation: 297

I found a working solution, but I am not sure if I'm satisfied with it:

  test_with_mock(
    "Find volunteer for list with two elements",
    Roller,
    [roll: fn () -> mock_roll end]
  ) do
    send self(), {:mock_return, 1}
    send self(), {:mock_return, 5}

    assert VolunteerFinder.find(["Sergey", "Borys"])
    == [%{name: "Borys", score: 5}, %{name: "Sergey", score: 1}]
  end

  def mock_roll do
    receive do
      {:mock_return, value} ->
        value
    after
      0 ->
        raise "No mocked returns received"
    end
  end

Is there any more elegant way of solving the problem?

Upvotes: 1

Related Questions