View Source Mimic (Mimic v1.11.1)
Mimic is a library that simplifies the usage of mocks in Elixir.
Mimic is mostly API compatible with mox but doesn't require explicit contract checking with behaviours. It's also faster. You're welcome.
Mimic works by copying your module out of the way and replacing it with one of it's own which can delegate calls back to the original or to a mock function as required.
In order to prepare a module for mocking you must call copy/1
with the
module as an argument. We suggest that you do this in your
test/test_helper.exs
:
Mimic.copy(Calculator)
ExUnit.start()
Importantly calling copy/1
will not change the behaviour of the module. When
writing tests you can then use stub/3
or expect/3
to add mocks and
assertions.
Multi-process collaboration
Mimic supports multi-process collaboration via two mechanisms:
- Explicit allows.
- Global mode.
Using explicit allows is generally preferred as these stubs can be run concurrently, whereas global mode tests must be run exclusively.
Explicit allows
Using allow/3
you can give other processes permission to use stubs and
expectations from where they were not defined.
test "invokes add from a process" do
Calculator
|> expect(:add, fn x, y -> x + y end)
parent_pid = self()
spawn_link(fn ->
Calculator |> allow(parent_pid, self())
assert Calculator.add(2, 3) == 5
send parent_pid, :ok
end)
assert_receive :ok
end
If you are using Task
the expectations and stubs are automatically allowed
Global mode
When set in global mode any process is able to call the stubs and expectations defined in your tests.
Warning: If using global mode you should remove async: true
from your tests
Enable global mode using set_mimic_global/1
.
setup :set_mimic_global
setup :verify_on_exit!
test "invokes add from a task" do
Calculator
|> expect(:add, fn x, y -> x + y end)
Task.async(fn ->
assert Calculator.add(2, 3) == 5
end)
|> Task.await
end
Summary
Functions
Allow other processes to share expectations and stubs defined by another process.
Call original implementation of a function.
Prepare module
for mocking.
Define a stub which must be called within an example.
Returns the current mode (:global
or :private
)
Define a stub which must not be called.
Define a stub which must not be called.
Chooses the mode based on ExUnit context. If async
is true
then
the mode is private, otherwise global.
Sets the mode to global. Mocks can be set and used by all processes
Sets the mode to private. Mocks can be set and used by the process
Replace all public functions in module
with stubs.
Define a stub function for a copied module.
Replace all public functions in module
with public function in mocking_module
.
Verify if expectations were fulfilled for a process pid
Verifies the current process after it exits.
Functions
Allow other processes to share expectations and stubs defined by another process.
Arguments:
module
- the copied module.owner_pid
- the process ID of the process which created the stub.allowed_pid
- the process ID of the process which should also be allowed to use this stub.
Raises:
- If Mimic is running in global mode.
Allows other processes to share expectations and stubs defined by another process.
Example
test "invokes add from a task" do
Calculator
|> expect(:add, fn x, y -> x + y end)
parent_pid = self()
Task.async(fn ->
Calculator |> allow(parent_pid, self())
assert Calculator.add(2, 3) == 5
end)
|> Task.await
end
Call original implementation of a function.
This function allows you to call the original implementation of a function, even if it has been stubbed, rejected or expected.
Arguments:
module
- the name of the module in which we're calling.function_name
- the name of the function we're calling.args
- the arguments of the function we're calling.
Raises:
- If
function_name
does not exist inmodule
.
Example:
iex> Mimic.call_original(Calculator, :add, [1, 2])
3
Prepare module
for mocking.
Ideally, don't call this function twice for the same module, but in case you do, this function
is idempotent. It will not delete any stub
s or expect
s that you've set up.
Arguments:
module
- the name of the module to copy.opts
- Extra options
Options:
type_check
- Must be a boolean defaulting tofalse
. Iftrue
the arguments and return value are validated against the module typespecs or the callback typespecs in case of a behaviour implementation.
@spec expect(atom(), atom(), non_neg_integer(), function()) :: module()
Define a stub which must be called within an example.
This function is almost identical to stub/3
except that the replacement
function must be called within the lifetime of the calling pid
(i.e. the
test example).
Arguments:
module
- the name of the module in which we're adding the stub.function_name
- the name of the function we're stubbing.function
- the function to use as a replacement.
Raises:
- If
module
is not copied. - If
function_name
is not publicly exported frommodule
with the same arity. - If
function
is not called by the stubbing process.
Example
iex> Calculator.add(2, 4)
6
iex> Mimic.expect(Calculator, :add, fn x, y -> x * y end)
...> Calculator.add(2, 4)
8
@spec mode() :: :private | :global
Returns the current mode (:global
or :private
)
Define a stub which must not be called.
This function allows you do define a stub which must not be called during the course of this test. If it is called then the verification step will raise.
Arguments:
function
- A capture of the function which must not be called.
Raises:
- If
function
is not called by the stubbing process while callingverify!/1
.
Example:
iex> Mimic.reject(&Calculator.add/2)
Calculator
@spec reject(module(), atom(), non_neg_integer()) :: module()
Define a stub which must not be called.
This function allows you do define a stub which must not be called during the course of this test. If it is called then the verification step will raise.
Arguments:
module
- the name of the module in which we're adding the stub.function_name
- the name of the function we're stubbing.arity
- the arity of the function we're stubbing.
Raises:
- If
function
is not called by the stubbing process while callingverify!/1
.
Example:
iex> Mimic.reject(Calculator, :add, 2)
Calculator
@spec set_mimic_from_context(map()) :: :ok
Chooses the mode based on ExUnit context. If async
is true
then
the mode is private, otherwise global.
setup :set_mimic_from_context
@spec set_mimic_global(map()) :: :ok
Sets the mode to global. Mocks can be set and used by all processes
setup :set_mimic_global
@spec set_mimic_private(map()) :: :ok
Sets the mode to private. Mocks can be set and used by the process
setup :set_mimic_private
Replace all public functions in module
with stubs.
The stubbed functions will raise if they are called.
Arguments:
module
- The name of the module to stub.
Raises:
- If
module
is not copied. - If
function
is not called by the stubbing process.
Example
iex> Mimic.stub(Calculator)
...> Calculator.add(2, 4)
** (ArgumentError) Module Calculator has not been copied. See docs for Mimic.copy/1
Define a stub function for a copied module.
Arguments:
module
- the name of the module in which we're adding the stub.function_name
- the name of the function we're stubbing.function
- the function to use as a replacement.
Raises:
- If
module
is not copied. - If
function_name
is not publicly exported frommodule
with the same arity.
Example
iex> Calculator.add(2, 4)
6
iex> Mimic.stub(Calculator, :add, fn x, y -> x * y end)
...> Calculator.add(2, 4)
8
Replace all public functions in module
with public function in mocking_module
.
If there's any public function in module
that are not in mocking_module
, it will raise if it's called
Arguments:
module
- The name of the module to stub.mocking_module
- The name of the mocking module to stub the original module.
Raises:
- If
module
is not copied. - If
function
is not called by the stubbing process.
Example
defmodule InverseCalculator do
def add(a, b), do: a - b
end
iex> Mimic.stub_with(Calculator, InverseCalculator)
...> Calculator.add(2, 4)
...> -2
@spec verify!(pid()) :: :ok
Verify if expectations were fulfilled for a process pid
Verifies the current process after it exits.
If you want to verify expectations for all tests, you can use
verify_on_exit!/1
as a setup callback:
setup :verify_on_exit!