Skip to content

Commit

Permalink
Add Mox.deny/3 (#146)
Browse files Browse the repository at this point in the history
Allow denying a call to a mock with clearer intentions.

Closes #145.
  • Loading branch information
rbino authored Nov 4, 2023
1 parent ccf1add commit 79cfb90
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 5 deletions.
38 changes: 33 additions & 5 deletions lib/mox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ defmodule Mox do
expect(MockWeatherAPI, :get_temp, 5, fn _ -> {:ok, 30} end)
To expect `MockWeatherAPI.get_temp/1` not to be called:
To expect `MockWeatherAPI.get_temp/1` not to be called (see also `deny/3`):
expect(MockWeatherAPI, :get_temp, 0, fn _ -> {:ok, 30} end)
Expand Down Expand Up @@ -538,7 +538,31 @@ defmodule Mox do
def expect(mock, name, n \\ 1, code)
when is_atom(mock) and is_atom(name) and is_integer(n) and n >= 0 and is_function(code) do
calls = List.duplicate(code, n)
add_expectation!(mock, name, code, {n, calls, nil})
arity = arity(code)
add_expectation!(mock, name, arity, {n, calls, nil})
mock
end

@doc """
Ensures that `name`/`arity` in `mock` is not invoked.
When `deny/3` is invoked, any previously declared `stub` for the same `name` and arity will
be removed. This ensures that `deny` will fail if the function is called. If a `stub/3` is
invoked **after** `deny/3` for the same `name` and `arity`, the stub will be used instead, so
`deny` will have no effect.
## Examples
To expect `MockWeatherAPI.get_temp/1` to never be called:
deny(MockWeatherAPI, :get_temp, 1)
"""
@doc since: "1.2.0"
@spec deny(mock, atom(), non_neg_integer()) :: mock when mock: t()
def deny(mock, name, arity)
when is_atom(mock) and is_atom(name) and is_integer(arity) and arity >= 0 do
add_expectation!(mock, name, arity, {0, [], nil})
mock
end

Expand All @@ -563,7 +587,8 @@ defmodule Mox do
@spec stub(mock, atom(), function()) :: mock when mock: t()
def stub(mock, name, code)
when is_atom(mock) and is_atom(name) and is_function(code) do
add_expectation!(mock, name, code, {0, [], code})
arity = arity(code)
add_expectation!(mock, name, arity, {0, [], code})
mock
end

Expand Down Expand Up @@ -631,9 +656,12 @@ defmodule Mox do
|> List.flatten()
end

defp add_expectation!(mock, name, code, value) do
defp arity(code) do
:erlang.fun_info(code)[:arity]
end

defp add_expectation!(mock, name, arity, value) do
validate_mock!(mock)
arity = :erlang.fun_info(code)[:arity]
key = {mock, name, arity}

unless function_exported?(mock, name, arity) do
Expand Down
55 changes: 55 additions & 0 deletions test/mox_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,61 @@ defmodule MoxTest do
end
end

describe "deny/3" do
test "allows asserting that function is not called" do
deny(CalcMock, :add, 2)

msg = ~r"expected CalcMock.add/2 to be called 0 times but it has been called once"

assert_raise Mox.UnexpectedCallError, msg, fn ->
CalcMock.add(2, 3) == 5
end
end

test "raises if a non-mock is given" do
assert_raise ArgumentError, ~r"could not load module Unknown", fn ->
deny(Unknown, :add, 2)
end

assert_raise ArgumentError, ~r"module String is not a mock", fn ->
deny(String, :add, 2)
end
end

test "raises if function is not in behaviour" do
assert_raise ArgumentError, ~r"unknown function oops/2 for mock CalcMock", fn ->
deny(CalcMock, :oops, 2)
end

assert_raise ArgumentError, ~r"unknown function add/3 for mock CalcMock", fn ->
deny(CalcMock, :add, 3)
end
end

test "raises even when a stub is defined" do
stub(CalcMock, :add, fn _, _ -> :stub end)
deny(CalcMock, :add, 2)

assert_raise Mox.UnexpectedCallError, fn ->
CalcMock.add(2, 3)
end
end

test "raises if you try to add expectations from non global process" do
set_mox_global()

Task.async(fn ->
msg =
~r"Only the process that set Mox to global can set expectations/stubs in global mode"

assert_raise ArgumentError, msg, fn ->
deny(CalcMock, :add, 2)
end
end)
|> Task.await()
end
end

describe "verify!/0" do
test "verifies all mocks for the current process in private mode" do
set_mox_private()
Expand Down

0 comments on commit 79cfb90

Please sign in to comment.