Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Mox.get_executed_calls/1 #140

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions lib/mox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, is this safe to execute concurrently? It doesn't look like it is the case. :(

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It definitely isn't 😉 Will have to figure it out with the allowance.

end

defp verify_mock_or_all!(pid, mock, test_or_on_exit) do
pending = Mox.Server.verify(pid, mock, test_or_on_exit)

Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means we are copying all argument data to a separate process. :(

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, is that an issue in tests? We would do the same when using the send/2 trick to send calls to the parent process.

Any ideas on how to do this without copying the args?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem is that this enables this for everything by default. We would need to store it somewhere if we want to collect or not.

:no_expectation ->
mfa = Exception.format_mfa(mock, name, arity)

Expand Down
26 changes: 21 additions & 5 deletions lib/mox/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -51,7 +55,8 @@ defmodule Mox.Server do
deps: %{},
mode: :private,
global_owner_pid: nil,
lazy_calls: false
lazy_calls: false,
executed_calls: %{}
}}
end

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions test/mox_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,66 @@ defmodule MoxTest do
end
end

describe "get_executed_calls/1" do
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")
Expand Down