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:

  1. Explicit allows.
  2. 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

Link to this function

allow(module, owner_pid, allowed_pid)

View Source
@spec allow(module(), pid(), pid()) :: module() | {:error, atom()}

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
Link to this function

call_original(module, function_name, args)

View Source
@spec call_original(module(), atom(), list()) :: any()

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 in module.

Example:

iex> Mimic.call_original(Calculator, :add, [1, 2])
3
Link to this function

copy(module, opts \\ [])

View Source
@spec copy(
  module(),
  keyword()
) :: :ok | no_return()

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 to false. If true the arguments and return value are validated against the module typespecs or the callback typespecs in case of a behaviour implementation.
Link to this function

expect(module, fn_name, num_calls \\ 1, func)

View Source
@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 from module 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)

@spec reject(function()) :: 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:

  • function - A capture of the function which must not be called.

Raises:

  • If function is not called by the stubbing process while calling verify!/1.

Example:

iex> Mimic.reject(&Calculator.add/2)
Calculator
Link to this function

reject(module, function_name, arity)

View Source
@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 calling verify!/1.

Example:

iex> Mimic.reject(Calculator, :add, 2)
Calculator
Link to this function

set_mimic_from_context(arg1)

View Source
@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
Link to this function

set_mimic_global(context \\ %{})

View Source
@spec set_mimic_global(map()) :: :ok

Sets the mode to global. Mocks can be set and used by all processes

setup :set_mimic_global
Link to this function

set_mimic_private(context \\ %{})

View Source
@spec set_mimic_private(map()) :: :ok

Sets the mode to private. Mocks can be set and used by the process

setup :set_mimic_private
@spec stub(module()) :: module()

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
Link to this function

stub(module, function_name, function)

View Source
@spec stub(module(), atom(), function()) :: module()

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 from module 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
Link to this function

stub_with(module, mocking_module)

View Source
@spec stub_with(module(), module()) :: module()

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

Link to this function

verify_on_exit!(context \\ %{})

View Source
@spec verify_on_exit!(map()) :: :ok | no_return()

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!