View Source Repatch (Repatch v1.0.0)

Repatch is a library for efficient, ergonomic and concise mocking/patching in tests (or not tests)

Features

  1. Patch any function or macro (except NIF and BIF). You can even patch and call private functions in test, or Erlang modules

  2. Designed to work with async: true. Has 3 isolation levels for testing multi-processes scenarios.

  3. Does not require any explicit boilerplate or DI. Though, you are completely free to use it with Repatch!

  4. Powerful call history tracking.

  5. super and real to help you call real implementations of the module.

  6. Works with other test frameworks and even in non-testing environments like iex or remote shell.

Installation

def deps do
  [
    {:repatch, "~> 1.0"}
  ]
end

One-minute intro

for ExUnit users

  1. Add Repatch.setup() into your test_helper.exs file after the ExUnit.start()

  2. use Repatch.ExUnit in your test module

  3. Call Repatch.patch or Repatch.fake to change implementation of any function and any module.

For example

defmodule ThatsATest do
  use ExUnit.Case, async: true
  use Repatch.ExUnit

  test "that's not a MapSet.new" do
    Repatch.patch(MapSet, :new, fn ->
      %{thats_not: :a_map_set}
    end)

    assert MapSet.new() == %{thats_not: :a_map_set}

    assert Repatch.called?(MapSet, :new, 1)
  end
end

Summary

Types

Options passed in the allow/3 function.

Options passed in the called?/4 function. When multiple options are specified, they are combined in logical AND fashion.

Options passed in the fake/3 function.

Mode of the patch or fake

Options passed in the patch/4 function.

Options passed in the repatched?/4 function.

Options passed in the restore/4 function.

Options passed in the setup/1 function.

Options passed in the spy/2 function.

Functions

Enables the allowed process to use the patch from owner process. Works only for patches in shared mode. See allow_option/0 for available options.

Lists all allowances of specified process (or self() by default). Works only when shared mode is enabled.

Checks if the function call is present in the history or not. Works with exact match on arguments or just an arity. Works only when history is enabled in setup. See called_check_option/0 for available options.

Cleans up current test process (or any other process) Repatch-state. It is recommended to be called during the test exit. Check out Repatch.ExUnit module which set up this callback up.

Replaces functions implementation of the real_module with functions of the fake_module.

For debugging purposes only. Returns list of tags which indicates patch state of the specified function.

Lists current owner of the allowed process (or self() by default). Works only when shared mode is enabled.

Substitutes implementation of the function with a new one. Starts tracking history on all calls in the module too. See patch_option/0 for available options.

Just a compiler-friendly wrapper to call private functions on the module. Works only on calls in Module.function(arg0, arg1, arg2) format.

Use this on a call which would result only to unpatched versions of functions to be called on the whole stack of calls. Works only on calls in Module.function(arg0, arg1, arg2) format.

Checks if function is patched in any (or some specific) mode.

Removes any patch or fake on the specified function. See restore_option/0 for available options.

Clears all state of the Repatch including all patches, fakes and history, and reloads all old modules back, disabling history collection on them. It is not recommended to be called during testing and it is suggested to be used only when Repatch is used in iex session.

Setup function. Use it only once per test suite. See setup_option/0 for available options.

Cleans the existing history of current process calls to this module and starts tracking new history of all calls to the specified module.

Use this on a call which would result only on one unpatched version of the function to be called. Works only on calls in Module.function(arg0, arg1, arg2) format.

Types

@type allow_option() :: {:force, boolean()}

Options passed in the allow/3 function.

  • force (boolean) — Whether to override existing allowance on the allowed process or not. Defaults to false.
@type called_check_option() ::
  {:by, pid() | :any}
  | {:at_least, :once | pos_integer()}
  | {:exactly, :once | pos_integer()}
  | {:after, monotonic_time_native :: integer()}
  | {:before, monotonic_time_native :: integer()}

Options passed in the called?/4 function. When multiple options are specified, they are combined in logical AND fashion.

  • by (:any | pid) — what process called the function. Defaults to self().

  • at_least (:once | integer) — at least how many times the function was called. Defaults to :once.

  • exactly (:once | integer) — exactly how many times the function was called.

  • before (:erlang.monotonic_time/0 timestamp) — upper boundary of when the function was called.
  • after (:erlang.monotonic_time/0 timestamp) — lower boundary of when the function was called.
@type fake_option() :: patch_option()

Options passed in the fake/3 function.

  • ignore_forbidden_module (boolean) — Whether to ignore the warning about forbidden module is being spied. Defaults to false.
  • force (boolean) — Whether to override existing patches and fakes. Defaults to false.
  • mode (:local | :shared | :global) — What mode to use for the fake. See mode/0 for more info. Defaults to :local.

@type mode() :: :local | :shared | :global

Mode of the patch or fake

@type patch_option() ::
  recompile_option() | {:mode, :local | :shared | :global} | {:force, boolean()}

Options passed in the patch/4 function.

  • ignore_forbidden_module (boolean) — Whether to ignore the warning about forbidden module is being spied. Defaults to false.
  • force (boolean) — Whether to override existing patches and fakes. Defaults to false.
  • mode (:local | :shared | :global) — What mode to use for the patch. See mode/0 for more info. Defaults to :local.

@type recompile_option() :: {:ignore_forbidden_module, boolean()}
Link to this type

repatched_check_option()

View Source
@type repatched_check_option() :: {:mode, mode() | :any}

Options passed in the repatched?/4 function.

  • mode (:local | :shared | :global | :any) — What mode to check the patch in. See mode/0 for more info. Defaults to :any.

@type restore_option() :: {:mode, mode()}

Options passed in the restore/4 function.

  • mode (:local | :shared | :global) — What mode to remove the patch in. See mode/0 for more info. Defaults to :local.

@type setup_option() ::
  recompile_option()
  | {:enable_global, boolean()}
  | {:enable_shared, boolean()}
  | {:enable_history, boolean()}
  | {:recompile, module() | [module()]}

Options passed in the setup/1 function.

  • enable_global (boolean) — Whether to allow global mocks in test suites. Defaults to false.
  • enable_shared (boolean) — Whether to allow shared mocks in test suites. Defaults to true.
  • enable_history (boolean) — Whether to enable calls history tracking. Defaults to true.
  • recompile (list of modules) — What modules should be recompiled before test starts. Modules are recompiled lazily by default. Defaults to [].
  • ignore_forbidden_module (boolean) — Whether to ignore the warning about forbidden module being recompiled. Works only when recompile is specified. Defaults to false.
@type spy_option() :: recompile_option() | {:by, pid()}

Options passed in the spy/2 function.

  • by (pid) — What process history to clean. Defaults to self().
  • ignore_forbidden_module (boolean) — Whether to ignore the warning about forbidden module is being spied. Defaults to false.
@type tag() :: :patched | mode()

Functions

Link to this function

allow(owner, allowed, opts \\ [])

View Source
@spec allow(pid(), pid(), [allow_option()]) :: :ok

Enables the allowed process to use the patch from owner process. Works only for patches in shared mode. See allow_option/0 for available options.

Link to this function

allowances(pid \\ self())

View Source
@spec allowances(pid()) :: [pid()]

Lists all allowances of specified process (or self() by default). Works only when shared mode is enabled.

Please note that deep allowances are returned as the final allowed process.

Link to this function

called?(module, function, arity_or_args, opts \\ [])

View Source
@spec called?(module(), atom(), arity() | [term()], [called_check_option()]) ::
  boolean()

Checks if the function call is present in the history or not. Works with exact match on arguments or just an arity. Works only when history is enabled in setup. See called_check_option/0 for available options.

@spec cleanup(pid()) :: :ok

Cleans up current test process (or any other process) Repatch-state. It is recommended to be called during the test exit. Check out Repatch.ExUnit module which set up this callback up.

Link to this function

fake(real_module, fake_module, opts \\ [])

View Source
@spec fake(module(), module(), [fake_option()]) :: :ok

Replaces functions implementation of the real_module with functions of the fake_module.

See fake_option/0 for available options.

Link to this function

info(module, function, arity, pid \\ self())

View Source
@spec info(module(), atom(), arity(), pid()) :: [tag()]
@spec info(module(), atom(), arity(), :any) :: %{required(pid()) => [tag()]}

For debugging purposes only. Returns list of tags which indicates patch state of the specified function.

@spec owner(pid()) :: pid() | nil

Lists current owner of the allowed process (or self() by default). Works only when shared mode is enabled.

Please note that deep allowances are returned as the final owner of the process.

Link to this function

patch(module, function, opts \\ [], func)

View Source
@spec patch(module(), atom(), [patch_option()], function()) :: :ok

Substitutes implementation of the function with a new one. Starts tracking history on all calls in the module too. See patch_option/0 for available options.

Be aware that it recompiles the module if it was not patched or spied on before, which may take some time.

Link to this macro

private(other)

View Source (macro)

Just a compiler-friendly wrapper to call private functions on the module. Works only on calls in Module.function(arg0, arg1, arg2) format.

Use this on a call which would result only to unpatched versions of functions to be called on the whole stack of calls. Works only on calls in Module.function(arg0, arg1, arg2) format.

Link to this function

repatched?(module, function, arity, opts \\ [])

View Source
@spec repatched?(module(), atom(), arity(), [repatched_check_option()]) :: boolean()

Checks if function is patched in any (or some specific) mode.

Link to this function

restore(module, function, arity, opts \\ [])

View Source
@spec restore(module(), atom(), arity(), [restore_option()]) :: :ok

Removes any patch or fake on the specified function. See restore_option/0 for available options.

@spec restore_all() :: :ok

Clears all state of the Repatch including all patches, fakes and history, and reloads all old modules back, disabling history collection on them. It is not recommended to be called during testing and it is suggested to be used only when Repatch is used in iex session.

@spec setup([setup_option()]) :: :ok

Setup function. Use it only once per test suite. See setup_option/0 for available options.

It is suggested to be put in the test_helper.exs after the ExUnit.start() line

@spec spy(module(), [spy_option()]) :: :ok

Cleans the existing history of current process calls to this module and starts tracking new history of all calls to the specified module.

Be aware that it recompiles the module if it was not patched or spied on before, which may take some time.

Use this on a call which would result only on one unpatched version of the function to be called. Works only on calls in Module.function(arg0, arg1, arg2) format.