defmodule Mock do @moduledoc """ Mock modules for testing purposes. Usually inside a unit test. ## Example defmodule MyTest do use ExUnit.Case import Mock test "get" do with_mock HTTPotion, [get: fn("http://example.com", _headers) -> HTTPotion.Response.new(status_code: 200, body: "hello") end] do # Code which calls HTTPotion.get # Check that the call was made as we expected assert called HTTPotion.get("http://example.com", :_) end end end """ @doc """ Mock up `mock_module` with functions specified as a keyword list of function_name:implementation `mocks` for the duration of `test`. `opts` List of optional arguments passed to meck. `:passthrough` will passthrough arguments to the original module. ## Example with_mock(HTTPPotion, [get: fn("http://example.com") -> "" end] do # Tests that make the expected call assert called HTTPotion.get("http://example.com") end """ defmacro with_mock(mock_module, opts \\ [], mocks, do: test) do quote do :meck.new(unquote(mock_module), unquote(opts)) unquote(__MODULE__)._install_mock(unquote(mock_module), unquote(mocks)) try do # Do all the tests inside so we can kill the mock # if any exception occurs. result = unquote(test) assert :meck.validate(unquote(mock_module)) == true result after :meck.unload(unquote(mock_module)) end end end @doc """ Shortcut to avoid multiple blocks when a test requires a single mock. For full description see `with_mock`. ## Example test_with_mock "test_name", HTTPotion, [get: fn(_url) -> ""] do HTTPotion.get("http://example.com") assert called HTTPotion.get("http://example.com") end """ defmacro test_with_mock(test_name, mock_module, opts \\ [], mocks, test_block) do quote do test unquote(test_name) do unquote(__MODULE__).with_mock( unquote(mock_module), unquote(opts), unquote(mocks), unquote(test_block)) end end end @doc """ Shortcut to avoid multiple blocks when a test requires a single mock. Accepts a context argument enabling information to be shared between callbacks and the test. For full description see `with_mock`. ## Example setup do doc = "" {:ok, doc: doc} end test_with_mock "test_with_mock with context", %{doc: doc}, HTTPotion, [], [get: fn(_url) -> doc end] do HTTPotion.get("http://example.com") assert called HTTPotion.get("http://example.com") end """ defmacro test_with_mock(test_name, context, mock_module, opts, mocks, test_block) do quote do test unquote(test_name), unquote(context) do unquote(__MODULE__).with_mock( unquote(mock_module), unquote(opts), unquote(mocks), unquote(test_block)) end end end @doc """ Use inside a `with_mock` block to determine whether a mocked function was called as expected. ## Example assert called HTTPotion.get("http://example.com") """ defmacro called({ {:., _, [ module , f ]} , _, args }) do quote do :meck.called unquote(module), unquote(f), unquote(args) end end @doc false def _install_mock(_, []), do: :ok def _install_mock(mock_module, [ {fn_name, value} | tail ]) do :meck.expect(mock_module, fn_name, value) _install_mock(mock_module, tail) end end