Packages

Implementation delegation without boilerplate

Current section

Files

Jump to
mockable lib mockable.ex
Raw

lib/mockable.ex

defmodule Mockable do
@moduledoc """
Documentation for `Mockable`.
"""
@doc """
Configures which module to use for a given mockable module.
If no implementation is provided, the mockable module itself will be used.
This is useful for testing your Mockable module.
```elixir
Mockable.use(Client)
```
Or to use a specific implementation:
```elixir
Mockable.use(Client, ClientMock)
```
This function stores the configuration in process memory so that it is compatible with async tests.
"""
def use(mockable, implementation \\ nil) do
implementation = implementation || mockable
Process.put({Mockable, mockable}, implementation)
end
defmacro __using__(_opts) do
quote do
@before_compile unquote(__MODULE__)
@behaviour __MODULE__
end
end
defmacro __before_compile__(env) do
module = env.module
module_string = module |> Atom.to_string() |> String.replace_prefix("Elixir.", "")
if Application.get_all_env(:mockable) != [] do
callbacks =
Module.get_attribute(module, :callback)
|> Enum.map(fn {:callback, {:"::", _, [{name, _, args} | _]}, _} ->
{name, length(args)}
end)
functions =
Module.definitions_in(module, :def)
|> Enum.filter(&(&1 in callbacks))
wrapped_functions =
for {name, arity} <- functions do
args = Macro.generate_arguments(arity, __MODULE__)
quote do
defoverridable [{unquote(name), unquote(arity)}]
def unquote(name)(unquote_splicing(args)) do
implementation =
Process.get({Mockable, unquote(module)}) ||
Application.get_env(:mockable, unquote(module))
log? = Application.get_env(:mockable, :log, true)
if implementation != unquote(module) do
log? &&
Logger.debug(
"Using #{inspect(implementation)}.#{unquote(name)}/#{unquote(arity)} for #{unquote(module_string)}"
)
apply(implementation, unquote(name), [unquote_splicing(args)])
else
log? &&
Logger.debug(
"Using #{inspect(unquote(module))}.#{unquote(name)}/#{unquote(arity)} for #{unquote(module_string)}"
)
super(unquote_splicing(args))
end
end
end
end
quote do
require Logger
(unquote_splicing(wrapped_functions))
end
end
end
end