Current section

Files

Jump to
espec lib espec mock.ex
Raw

lib/espec/mock.ex

defmodule ESpec.Mock do
@moduledoc """
Defines 'expect` function to mock function using 'meck'.
:meck.new is called with options :non_strict and :passthrough.
The :non_strict allows create mocks for modules and functions that do not exist.
The :passthrough options allow call other functions in the module.
Information about mock is stored in the ':espec_mock_agent' set.
Mock are being unloaded after each example.
"""
@agent_name :espec_mock_agent
@default_options [:non_strict, :passthrough]
@doc """
Creates new mock using :meck. The default :meck options are [:non_strict, :passthrough]
but they can be overriden.
Stores mock in agent to remove it after spec.
"""
def expect(module, name, function, meck_options) do
opts = if Enum.empty?(meck_options), do: @default_options, else: meck_options
try do
:meck.new(module, opts)
rescue
error in [ErlangError] ->
case error do
%ErlangError{original: {:already_started, _pid}} -> :ok
_ -> raise error
end
end
:meck.expect(module, name, function)
agent_put({module, self()})
end
@doc "Unloads modules at the end of example"
def unload do
modules = agent_get(self()) |> Enum.map(fn {m, _p} -> m end)
:meck.unload(modules)
agent_del(self())
end
@doc "Starts Agent to save mocked modules."
def start_agent, do: Agent.start_link(fn -> MapSet.new() end, name: @agent_name)
@doc "Stops Agent"
def stop_agent, do: Agent.stop(@agent_name)
defp agent_get(pid) do
Agent.get(@agent_name, & &1)
|> Enum.filter(fn {_m, p} -> p == pid end)
end
defp agent_del(pid) do
new_set =
Agent.get(@agent_name, & &1)
|> Enum.reduce(MapSet.new(), fn {m, p}, acc ->
if p != pid, do: MapSet.put(acc, {m, p}), else: acc
end)
Agent.update(@agent_name, fn _state -> new_set end)
end
defp agent_put(key), do: Agent.update(@agent_name, &MapSet.put(&1, key))
end