From 55865273ab0b1dfde01c9b50f8d2617c97186b6f Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Thu, 6 Jul 2023 14:49:36 +0200 Subject: [PATCH 1/2] Add Mox.get_executed_calls/1 --- lib/mox.ex | 9 +++++-- lib/mox/server.ex | 26 ++++++++++++++++---- test/mox_test.exs | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/lib/mox.ex b/lib/mox.ex index 60c40da..71e8978 100644 --- a/lib/mox.ex +++ b/lib/mox.ex @@ -189,7 +189,7 @@ defmodule Mox do when the allowance happens. In such a case, you might specify the allowance as a function in the form `(-> pid())`. This function would be resolved late, at the very moment of dispatch. If the function does not return an existing - PID, it will fail `Mox.UnexpectedCallError`. + PID, it will fail `Mox.UnexpectedCallError`. ### Global mode @@ -763,6 +763,11 @@ defmodule Mox do verify_mock_or_all!(self(), mock, :test) end + def get_executed_calls(mock) do + validate_mock!(mock) + Mox.Server.get_executed_calls(mock) + end + defp verify_mock_or_all!(pid, mock, test_or_on_exit) do pending = Mox.Server.verify(pid, mock, test_or_on_exit) @@ -812,7 +817,7 @@ defmodule Mox do def __dispatch__(mock, name, arity, args) do all_callers = [self() | caller_pids()] - case Mox.Server.fetch_fun_to_dispatch(all_callers, {mock, name, arity}) do + case Mox.Server.fetch_fun_to_dispatch(all_callers, {mock, name, arity}, args) do :no_expectation -> mfa = Exception.format_mfa(mock, name, arity) diff --git a/lib/mox/server.ex b/lib/mox/server.ex index 1520770..8e2433d 100644 --- a/lib/mox/server.ex +++ b/lib/mox/server.ex @@ -21,8 +21,8 @@ defmodule Mox.Server do GenServer.call(@this, {:add_expectation, owner_pid, key, value}, @timeout) end - def fetch_fun_to_dispatch(caller_pids, key) do - GenServer.call(@this, {:fetch_fun_to_dispatch, caller_pids, key, self()}, @timeout) + def fetch_fun_to_dispatch(caller_pids, key, args) do + GenServer.call(@this, {:fetch_fun_to_dispatch, caller_pids, key, args, self()}, @timeout) end def verify(owner_pid, for, test_or_on_exit) do @@ -41,6 +41,10 @@ defmodule Mox.Server do GenServer.call(@this, {:set_mode, owner_pid, mode}) end + def get_executed_calls(mock) do + GenServer.call(@this, {:get_executed_calls, mock}) + end + # Callbacks def init(:ok) do @@ -51,7 +55,8 @@ defmodule Mox.Server do deps: %{}, mode: :private, global_owner_pid: nil, - lazy_calls: false + lazy_calls: false, + executed_calls: %{} }} end @@ -119,7 +124,7 @@ defmodule Mox.Server do end def handle_call( - {:fetch_fun_to_dispatch, caller_pids, {mock, _, _} = key, source}, + {:fetch_fun_to_dispatch, caller_pids, {mock, function_name, _} = key, args, source}, %{mode: :private, lazy_calls: lazy_calls} = state ) do state = maybe_revalidate_lazy_calls(lazy_calls, state) @@ -141,16 +146,18 @@ defmodule Mox.Server do {:reply, {:out_of_expectations, total}, state} {_, [], stub} -> + state = update_in(state.executed_calls[mock], &((&1 || []) ++ [{function_name, args}])) {:reply, {ok_or_remote(source), stub}, state} {total, [call | calls], stub} -> + state = update_in(state.executed_calls[mock], &((&1 || []) ++ [{function_name, args}])) new_state = put_in(state.expectations[owner_pid][key], {total, calls, stub}) {:reply, {ok_or_remote(source), call}, new_state} end end def handle_call( - {:fetch_fun_to_dispatch, _caller_pids, {_mock, _, _} = key, source}, + {:fetch_fun_to_dispatch, _caller_pids, {mock, function_name, _} = key, args, source}, %{mode: :global} = state ) do case state.expectations[state.global_owner_pid][key] do @@ -161,9 +168,11 @@ defmodule Mox.Server do {:reply, {:out_of_expectations, total}, state} {_, [], stub} -> + state = update_in(state.executed_calls[mock], &((&1 || []) ++ [{function_name, args}])) {:reply, {ok_or_remote(source), stub}, state} {total, [call | calls], stub} -> + state = update_in(state.executed_calls[mock], &((&1 || []) ++ [{function_name, args}])) new_state = put_in(state.expectations[state.global_owner_pid][key], {total, calls, stub}) {:reply, {ok_or_remote(source), call}, new_state} end @@ -172,6 +181,8 @@ defmodule Mox.Server do def handle_call({:verify, owner_pid, mock, test_or_on_exit}, state) do expectations = state.expectations[owner_pid] || %{} + state = put_in(state.executed_calls, %{}) + pending = for {{module, _, _} = key, {count, [_ | _] = calls, _stub}} <- expectations, module == mock or mock == :all do @@ -233,6 +244,11 @@ defmodule Mox.Server do {:reply, :ok, %{state | mode: :private, global_owner_pid: nil}} end + def handle_call({:get_executed_calls, mock}, state) do + calls = Map.get(state.executed_calls, mock, []) + {:reply, calls, state} + end + # Helper functions defp reset_global_mode(state) do diff --git a/test/mox_test.exs b/test/mox_test.exs index 5743546..35162aa 100644 --- a/test/mox_test.exs +++ b/test/mox_test.exs @@ -948,6 +948,67 @@ defmodule MoxTest do end end + describe "get_executed_calls/1" do + @describetag :focus + test "returns all the calls executed on the given mock (in private mode)" do + set_mox_private() + + assert Mox.get_executed_calls(CalcMock) == [] + + expect(CalcMock, :add, fn x, y -> x + y end) + assert Mox.get_executed_calls(CalcMock) == [] + + assert CalcMock.add(2, 3) == 5 + expect(CalcMock, :add, fn x, y -> x + y end) + + expect(CalcMock, :add, 2, fn x, y -> x + y end) + assert CalcMock.add(3, 4) == 7 + assert CalcMock.add(4, 5) == 9 + assert Mox.get_executed_calls(CalcMock) == [{:add, [2, 3]}, {:add, [3, 4]}, {:add, [4, 5]}] + + stub(CalcMock, :add, fn x, y -> x + y end) + + for _ <- 1..3 do + assert CalcMock.add(1, -1) == 0 + end + + assert Mox.get_executed_calls(CalcMock) == + [{:add, [2, 3]}, {:add, [3, 4]}, {:add, [4, 5]}] ++ + List.duplicate({:add, [1, -1]}, 3) + + verify!(CalcMock) + end + + test "returns all the calls executed on the given mock (in global mode)" do + set_mox_global() + + assert Mox.get_executed_calls(CalcMock) == [] + + expect(CalcMock, :add, fn x, y -> x + y end) + assert Mox.get_executed_calls(CalcMock) == [] + + assert CalcMock.add(2, 3) == 5 + expect(CalcMock, :add, fn x, y -> x + y end) + + expect(CalcMock, :add, 2, fn x, y -> x + y end) + assert CalcMock.add(3, 4) == 7 + assert CalcMock.add(4, 5) == 9 + assert Mox.get_executed_calls(CalcMock) == [{:add, [2, 3]}, {:add, [3, 4]}, {:add, [4, 5]}] + + stub(CalcMock, :add, fn x, y -> x + y end) + + for _ <- 1..3 do + assert CalcMock.add(1, -1) == 0 + end + + assert Mox.get_executed_calls(CalcMock) == + [{:add, [2, 3]}, {:add, [3, 4]}, {:add, [4, 5]}] ++ + List.duplicate({:add, [1, -1]}, 3) + + verify!(CalcMock) + end + end + defp async_no_callers(fun) do Task.async(fn -> Process.delete(:"$callers") From 6186be1a075e432aa3b73382ec2aaf9e0fed3fba Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Thu, 6 Jul 2023 14:50:32 +0200 Subject: [PATCH 2/2] Remove @tag :focus --- test/mox_test.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/mox_test.exs b/test/mox_test.exs index 35162aa..f7ae78b 100644 --- a/test/mox_test.exs +++ b/test/mox_test.exs @@ -949,7 +949,6 @@ defmodule MoxTest do end describe "get_executed_calls/1" do - @describetag :focus test "returns all the calls executed on the given mock (in private mode)" do set_mox_private()