lib/dummy.ex

defmodule Dummy do
  @moduledoc """
  Documentation for Dummy.
  """
  alias Dummy.CallArgumentsError
  alias Dummy.Method

  def apply_options(module, options) do
    if options[:passthrough] == false do
      :meck.new(module)
    else
      :meck.new(module, [:passthrough])
    end
  end

  def is_called_function?(call_tuple, module, function) do
    if elem(call_tuple, 0) == module and elem(call_tuple, 1) == function do
      true
    else
      false
    end
  end

  def get_call_args(module, function) do
    last_call =
      module
      |> :meck.history()
      |> Enum.filter(fn item ->
        Dummy.is_called_function?(elem(item, 1), module, function)
      end)
      |> List.last()

    if last_call != nil do
      elem(elem(last_call, 1), 2)
    else
      []
    end
  end

  def assert_called(module, f, expected_args) do
    if :meck.called(module, f, expected_args) do
      true
    else
      call_args = Dummy.get_call_args(module, f)

      raise CallArgumentsError, [expected_args, call_args]
    end
  end

  defmacro called({{:., _, [module, f]}, _, expected_args}) do
    quote do
      Dummy.assert_called(unquote(module), unquote(f), unquote(expected_args))
    end
  end

  @doc """
  Mocks methods of a single module. Mocked methods return their
  first argument by default and non-mocked methods are passed through.
  """
  defmacro dummy(module_name, methods_list, options \\ [], do: test) do
    quote do
      module = unquote(module_name)
      methods = unquote(methods_list)
      apply_options(module, unquote(options))

      for method <- methods, do: Method.replace(module, method)

      try do
        unquote(test)
      after
        :meck.unload(module)
      end
    end
  end
end