# `Mimic`
[🔗](https://github.com/edgurgel/mimic/blob/v2.4.2/lib/mimic.ex#L1)

Mimic is a library that simplifies the usage of mocks in Elixir.

Mimic is mostly API compatible with [mox](https://hex.pm/packages/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`:

```elixir
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.

```elixir
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`.

```elixir
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
```

# `allow`

```elixir
@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

```elixir
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`

```elixir
@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

# `calls`

```elixir
@spec calls(function()) :: [[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:

  * `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]]

# `calls`

```elixir
@spec calls(module(), atom(), non_neg_integer()) :: [[any()]]
```

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

# `copy`

```elixir
@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 `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 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.

# `expect`

```elixir
@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 `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

# `mode`

```elixir
@spec mode() :: :private | :global
```

Returns the current mode (`:global` or `:private`)

# `reject`

```elixir
@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 called by the stubbing process while calling `verify!/1`.

## Example:

    iex> Mimic.reject(&Calculator.add/2)
    Calculator

# `reject`

```elixir
@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 called by the stubbing process while calling `verify!/1`.

## Example:

    iex> Mimic.reject(Calculator, :add, 2)
    Calculator

# `set_mimic_from_context`

```elixir
@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.

```elixir
setup :set_mimic_from_context
```

# `set_mimic_global`

```elixir
@spec set_mimic_global(map()) :: :ok
```

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

```elixir
setup :set_mimic_global
```

Must be called from the test process, since global mode is released by an
`ExUnit.Callbacks.on_exit/2` callback. Raises otherwise.

# `set_mimic_private`

```elixir
@spec set_mimic_private(map()) :: :ok
```

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

```elixir
setup :set_mimic_private
```

# `stub`

```elixir
@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

# `stub`

```elixir
@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

# `stub_with`

```elixir
@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

# `verify!`

```elixir
@spec verify!(pid()) :: :ok
```

Verify if expectations were fulfilled for a process `pid`

# `verify_on_exit!`

```elixir
@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:

```elixir
setup :verify_on_exit!
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
