Mimic (Mimic v1.7.4) View Source

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
  Caculator
  |> 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

Link to this section Summary

Functions

Allow other processes to share expectations and stubs defined by another process.

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.

Link to this section Functions

Link to this function

allow(module, owner_pid, allowed_pid)

View Source

Specs

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
  Caculator
  |> 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

Specs

copy(module()) :: :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.
Link to this function

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

View Source

Specs

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

Specs

mode() :: :private | :global

Returns the current mode (:global or :private)

Specs

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

Specs

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

Specs

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

Specs

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

Specs

set_mimic_private(map()) :: :ok

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

setup :set_mimic_private

Specs

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

Specs

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

Specs

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

Specs

verify!(pid()) :: :ok

Verify if expectations were fulfilled for a process pid

Link to this function

verify_on_exit!(context \\ %{})

View Source

Specs

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!