View Source Mimic (Mimic v2.1.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
endIf 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.
Get the list of calls made to a mocked/stubbed function.
Get the list of calls made to a mocked/stubbed 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_namedoes not exist inmodule.
Example:
iex> Mimic.call_original(Calculator, :add, [1, 2])
3
Get the list of calls made to a mocked/stubbed function.
This function returns a list of all arguments passed to the function during each call. If the function has not been mocked/stubbed or has not been called, it will raise an error.
Arguments:
function- A capture of the function to get the calls for.
Returns:
- A list of lists, where each inner list contains the arguments from one call.
Raises:
- If the function has not been mocked/stubbed.
- If the function does not exist in the module.
Example:
iex> Calculator.add(1, 2)
3
iex> Mimic.calls(&Calculator.add/2)
[[1, 2]]
@spec calls(module(), atom(), non_neg_integer()) :: [[any()]] | {:error, :atom}
Get the list of calls made to a mocked/stubbed function.
This function returns a list of all arguments passed to the function during each call. If the function has not been mocked/stubbed or has not been called, it will raise an error.
Arguments:
module- the name of the module containing the function.function_name- the name of the function.arity- the arity of the function.
Returns:
- A list of lists, where each inner list contains the arguments from one call.
Raises:
- If the function has not been mocked/stubbed.
- If the function does not exist in the module.
Example:
iex> Calculator.add(1, 2)
3
iex> Mimic.calls(Calculator, :add, 2)
[[1, 2]]
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 stubs or expects 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. Iftruethe 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).
Once the expectation is fulfilled any calls to this MFA will raise an error
unless there is a stub through stub/3
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
moduleis not copied. - If
function_nameis not publicly exported frommodulewith the same arity. - If
functionis 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
functionis 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
functionis 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
moduleis not copied. - If
functionis 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
moduleis not copied. - If
function_nameis not publicly exported frommodulewith 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
moduleis not copied. - If
functionis 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!