elixir_mock v0.2.9 ElixirMock

This module contains functions and macros for creating mocks from real modules. It also contains utilities for verifying that calls were made to functions in the mocks.

Link to this section Summary

Functions

Verifies that a function on a mock was called.

Creates a mock module from a real module allowing custom definitons for some or all of the functions on the mock.

Creates a mock module from a real module just like defmock_of/2 but additionally allows a context map to be injected into the mock definition.

Creates mock from real module with all functions on real module defined on the the mock.

Verifies that a function on a mock was not called.

A light wrapper that assigns names to mocks created with the defmock_of/3 and defmock_of/2 macros.

Link to this section Functions

Link to this macro

assert_called(function_call_expression)

(macro)

Verifies that a function on a mock was called.

defmodule MyTest do
  use ExUnit.Case
  require ElixirMock
  import ElixirMock

  test "verifies that function on mock was called" do
    mock = mock_of List
    mock.first [1, 2]
    assert_called mock.first([1, 2]) # passes
    assert_called mock.first(:some_other_arg) # fails!
  end
end

Note that the function call expressions passed to the macro are not executed. Rather, they are deconstructed to get the function name and the arguments. The function name and arguments are then used to find the call in the mocks recorded list of calls.

When assert_called/1 is given a matcher, the macro makes the test pass if the matcher evaluates to true for any recorded call. See ElixirMock.Matchers for more juicy details on Matchers.

defmodule MyTest do
  use ExUnit.Case
  require ElixirMock
  import ElixirMock
  alias ElixirMock.Matchers

  test "verifies that function on mock was called" do
    mock = mock_of List
    mock.first [1, 2]
    assert_called mock.first(Matchers.any(:list)) # passes
    assert_called mock.first(Matchers.any(:atom)) # fails!
  end
end
Link to this macro

defmock_of(real_module, list)

(macro)

Creates a mock module from a real module allowing custom definitons for some or all of the functions on the mock.

Mock behaviour can be tuned in a number of different ways depending on your needs. The next few sections enumerate the tuning options available with examples. We will use the inbuilt List module as our base module for these examples.

Feature: Overriding functions

Creating a mock from the List module and overriding its List.first/1 function.

require ElixirMock
import ElixirMock

with_mock(list_mock) = defmock_of List do
  def first(_list), do: :mock_response_from_first
end

list_mock.first([1, 2]) == :mock_response_from_first
#=> true

Feature: Delegating calls to the real module with :call_through

When a function in a mock defintion returns the atom :call_through, ElixirMock will forward all calls made to that function to the corresponding function on the real module. All calls are still recorded by the mock and are inspectable with the assert_called/1 and refute_called/1 macros.

require ElixirMock
import ElixirMock

with_mock(list_mock) = defmock_of List do
  def first(_list), do: :call_through
end

list_mock.first([1, 2]) == List.first([1, 2]) == 1
#=> true

Feature: Delegating unspecified function calls to the real module

Sometimes, you only want to stub out specific functions on modules but leave other functions behaving as defined on the original module. This could be because the overriden functions have side-effects you don't want to deal with in your tests or because you want to alter the behaviour of just those functions so you can test code that depends on them. ElixirMock provides the @call_through_undeclared_functions mock attribute to help with this. Mocks defined with this attribute set to true will forward calls made to undeclared functions to the real module. Mocks defined without this attribute simply return nil when calls are made to undeclared functions.

All functions calls, whether defined on the mock on not, are still recorded by the mock and are inspectable with the assert_called/1 and refute_called/1 macros.

In the example below, the List.first/1 function is overriden but the List.last/1 function retains its original behaviour.

require ElixirMock
import ElixirMock

with_mock(list_mock) = defmock_of List do
  @call_through_undeclared_functions true
  def first(_list), do: :mock_response_from_first
end

list_mock.first([1, 2]) == :mock_response_from_first
list_mock.last([1, 2] == List.last([1, 2]) == 2
#=> true

Info: Mock functions only override function heads of the same arity in the real module.

defmodule Real do
  def x, do: {:arity, 0}
  def x(_arg), do: {:arity, 1}
end

with_mock(mock) = defmock_of Real do
  def x, do: :overridden_x
end

mock.x == :overridden_x
#=> true
mock.x(:some_arg) == nil
#=> true

Notes

  • An ElixirMock.MockDefinitionError is raised if a public function that does not exist in the real module is declared on the mock.
  • Mocks allow private functions to be defined on them. These functions needn't be defined on the real module. In fact, private functions are not imported from the real module into the mock at all.
  • Please refer to the Getting started guide for a broader enumeration of the characteristics of ElixirMock's mocks.
Link to this macro

defmock_of(real_module, context \\ {:%{}, [], []}, list)

(macro)

Creates a mock module from a real module just like defmock_of/2 but additionally allows a context map to be injected into the mock definition.

The context injected in the mock is accessible to the functions within the mock definition via the ElixirMock.Mock.context/2 function. It takes in the context key and a mock and looks up the key's value in the context map passed to mock when it was defined. An ArgumentError is thrown if the key doesn't exist in the context map.

Being able to pass context into mocks is very useful when you need to fix the behaviour of a mock using some values declared outside the mock's definition.

Example:

require ElixirMock
import ElixirMock

fixed_first = 100

with_mock(list_mock) = defmock_of List, %{fixed_first: fixed_first} do
  def first(_list), do: ElixirMock.Mock.context(:fixed_first, __MODULE__)
end

list_mock.first([1, 2]) == fixed_first
#=> true

For more on the options available within mock definitions, see defmock_of/2

Link to this function

mock_of(real_module, call_through \\ false)

mock_of(module(), atom()) :: ElixirMock.Mock.mock()

Creates mock from real module with all functions on real module defined on the the mock.

By default, all functions on the mock return nil. The behaviour of the module the mock is defined from remains intact.

defmodule MyRealModule do
  def function_one(_), do: :real_result
end

require ElixirMock
import ElixirMock

my_mock = mock_of MyRealModule

# functions on mock return nil
my_mock.function_one(1) == nil
#=> true

# the real module is still intact
MyRealModule.function_one(1) == :real_result
#=> true

call_through

When :call_through is provided, functions defined on the mock delegate all calls to the corresponding functions on the real module.

transparent_mock = mock_of MyRealModule, :call_through
transparent_mock.function_one(1) == MyRealModule.function_one(1) == :real_result
#=> true
Link to this macro

refute_called(function_call_expression)

(macro)

Verifies that a function on a mock was not called.

defmodule MyTest do
  use ExUnit.Case
  require ElixirMock
  import ElixirMock

  test "verifies that function on mock was not called" do
    mock = mock_of List
    mock.first [1, 2]
    refute_called mock.first(:some_other_arg) # passes
    refute_called mock.first([1, 2]) # fails!
  end
end

Note that the function call expressions passed to the macro are not executed. Rather, they are deconstructed to get the function name and the arguments. The function name and arguments are then used to find the call in the mocks recorded list of calls.

When refute_called/1 is given a matcher, the macro makes the test pass if the matcher evaluates to false for all recorded calls. See ElixirMock.Matchers for more juicy details on Matchers.

defmodule MyTest do
  use ExUnit.Case
  require ElixirMock
  import ElixirMock
  alias ElixirMock.Matchers

  test "verifies that function on mock was not called" do
    mock = mock_of List
    mock.first [1, 2]
    refute_called mock.first(Matchers.any(:number)) # passes
    refute_called mock.first(Matchers.any(:list)) # fails!
  end
end
Link to this macro

with_mock(mock_name)

(macro)

A light wrapper that assigns names to mocks created with the defmock_of/3 and defmock_of/2 macros.

This is necessary because defmock_of/3 and defmock_of/2 return random mock module names wrapped in an ast tuple. This little macro helps you give the random mock module a human-friendly name.

Example:

require ElixirMock
import ElixirMock

with_mock(my_custom_mock) = defmock_of List do end

# you can then use 'my_custom_mock' as a normal module
my_custom_mock.first([1, 2])
#=> nil