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
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
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.
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
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
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
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