Copyright © 2011, Klas Johansson
Behaviours: gen_fsm.
Authors: Klas Johansson.
Mock functions and modules
In order to use the various macros below, mocking must be
initiated using the ?MOCK
macro or ?WITH_MOCKED_SETUP
(recommended from eunit tests).
?MOCK(Expr)
where Expr
in a single expression, like a fun. The rest of the
macros in this module can be used within this fun or in a function
called by the fun.
?WITH_MOCKED_SETUP(SetupFun, CleanupFun),
?WITH_MOCKED_SETUP(SetupFun, CleanupFun, ForAllTimeout, PerTcTimeout),
?WITH_MOCKED_SETUP(SetupFun, CleanupFun, ForAllTimeout, PerTcTimeout,
Tests),
This is an easy way of using mocks from within eunit tests and is
mock-specific version of the ?WITH_SETUP
macro. See the docs
for the ?WITH_SETUP
macro in the eunit_addons
project for more
information on parameters and settings.
By mocking a function, its original side-effects and return value (or throw/exit/error) are overridden and replaced. This can be used to:
BIFs (built-in functions) cannot be mocked.
The original module will be renamed (a "^" will be appended to the
original module name, i.e. foo
will be renamed to 'foo^'
).
A mock can then call the original function just by performing a regular
function call.
Since WHEN is a macro, and macros don't support argument lists
(something like "Arg..."), multi-expression mocks must be
surrounded by begin ... end
to be treated as one argument by the
preprocessor.
A mock that was introduced using the ?WHEN macro can be forgotten,
i.e. returned to the behaviour of the original module, using the
?FORGET_WHEN
macro.
?WHEN(module:function(Arg1, Arg2, ...) -> Expr),
where Expr
is a single expression (like a term) or a series of
expressions surrounded by begin
and end
.
?FORGET_WHEN(module:function(_, _, ...)),
The only things of interest are the name of the module, the name of the function and the arity. The arguments of the function are ignored and it can be a wise idea to set these to the "don't care" variable: underscore.
Note: Apparently the Erlang/OTP team doesn't want us to redefine PI to 4 anymore :-), since starting at R15B, math:pi/0 is marked as pure which means that the compiler is allowed to replace the math:pi() function call by a constant: 3.14... This means that even though mockgyver can mock the pi/0 function, a test case will never call math:pi/0 since it will be inlined. See commit 5adf009cb09295893e6bb01b4666a569590e0f19 (compiler: Turn calls to math:pi/0 into constant values) in the otp sources.
Redefine pi to 4:
?WHEN(math:pi() -> 4),
Implement a mock with multiple clauses:
?WHEN(my_module:classify_number(N) when N >= 0 -> positive;
my_module:classify_number(_N) -> negative),
Call original module:
?WHEN(math:pi() -> 'math^':pi() * 2),
Use a variable bound outside the mock:
Answer = 42,
?WHEN(math:pi() -> Answer),
Redefine the mock:
?WHEN(math:pi() -> 4),
4 = math:pi(),
?WHEN(math:pi() -> 5),
5 = math:pi(),
Let the mock exit with an error:
?WHEN(math:pi() -> erlang:error(some_error)),
Make a new module:
?WHEN(my_math:pi() -> 4),
?WHEN(my_math:e() -> 3),
Put multiple clauses in a function's body:
?WHEN(math:pi() ->
begin
do_something1(),
do_something2()
end),
Revert the pi function to its default behaviour (return value from
the original module), any other mocks in the same module, or any
other module are left untouched:
?WHEN(math:pi() -> 4),
4 = math:pi(),
?FORGET_WHEN(math:pi()),
3.1415... = math:pi(),
There are a number of ways to check that a certain function has been called and that works for both mocks and non-mocks.
?WAS_CALLED
: Check that a function was called with
certain set of parameters a chosen number of times.
The validation is done at the place of the macro, consider
this when verifying asynchronous procedures
(see also ?WAIT_CALLED
). Return a list of argument lists,
one argument list for each call to the function. An
argument list contains the arguments of a specific call.
Will crash with an error if the criteria isn't fulfilled.?WAIT_CALLED
: Same as ?WAS_CALLED
, with a twist: waits for
the criteria to be fulfilled which can be useful for
asynchrounous procedures.?GET_CALLS
: Return a list of argument lists (just like
?WAS_CALLED
or ?WAIT_CALLED
) without checking any criteria.?NUM_CALLS
: Return the number of calls to a function.?FORGET_CALLS
: Forget the calls that have been logged.
This exists in two versions:
?WAS_CALLED(module:function(Arg1, Arg2, ...)),
equivalent to ?WAS_CALLED(module:function(Arg1, Arg2, ...), once)
?WAS_CALLED(module:function(Arg1, Arg2, ...), Criteria),
Criteria = once | never | {times, N} | {at_least, N} | {at_most, N}
N = integer()
Result: [CallArgs]
CallArgs = [CallArg]
CallArg = term()
See syntax for ?WAS_CALLED
.
?GET_CALLS(module:function(Arg1, Arg2, ...)),
Result: [CallArgs]
CallArgs = [CallArg]
CallArg = term()
?NUM_CALLS(module:function(Arg1, Arg2, ...)),
Result: integer()
?FORGET_CALLS(module:function(Arg1, Arg2, ...)),
?FORGET_CALLS(),
?WAS_CALLED(math:pi()),
?WAS_CALLED(math:pi(), once),
Check that a function has never been called:
?WAS_CALLED(math:pi(), never),
Check that a function has been called twice:
?WAS_CALLED(math:pi(), {times, 2}),
Check that a function has been called at least twice:
?WAS_CALLED(math:pi(), {at_least, 2}),
Check that a function has been called at most twice:
?WAS_CALLED(math:pi(), {at_most, 2}),
Use pattern matching to check that a function was called with
certain arguments:
?WAS_CALLED(lists:reverse([a, b, c])),
Pattern matching can even use bound variables:
L = [a, b, c],
?WAS_CALLED(lists:reverse(L)),
Use a guard to validate the parameters in a call:
?WAS_CALLED(lists:reverse(L) when is_list(L)),
Retrieve the arguments in a call while verifying the number of calls:
a = lists:nth(1, [a, b]),
d = lists:nth(2, [c, d]),
[[1, [a, b]], [2, [c, d]]] = ?WAS_CALLED(lists:nth(_, _), {times, 2}),
Retrieve the arguments in a call without verifying the number of calls:
a = lists:nth(1, [a, b]),
d = lists:nth(2, [c, d]),
[[1, [a, b]], [2, [c, d]]] = ?GET_CALLS(lists:nth(_, _)),
Retrieve the number of calls:
a = lists:nth(1, [a, b]),
d = lists:nth(2, [c, d]),
2 = ?NUM_CALLS(lists:nth(_, _)),
Forget calls to functions:
a = lists:nth(1, [a, b, c]),
e = lists:nth(2, [d, e, f]),
i = lists:nth(3, [g, h, i]),
?WAS_CALLED(lists:nth(1, [a, b, c]), once),
?WAS_CALLED(lists:nth(2, [d, e, f]), once),
?WAS_CALLED(lists:nth(3, [g, h, i]), once),
?FORGET_CALLS(lists:nth(2, [d, e, f])),
?WAS_CALLED(lists:nth(1, [a, b, c]), once),
?WAS_CALLED(lists:nth(2, [d, e, f]), never),
?WAS_CALLED(lists:nth(3, [g, h, i]), once),
?FORGET_CALLS(lists:nth(_, _)),
?WAS_CALLED(lists:nth(1, [a, b, c]), never),
?WAS_CALLED(lists:nth(2, [d, e, f]), never),
?WAS_CALLED(lists:nth(3, [g, h, i]), never),
Forget calls to all functions:
a = lists:nth(1, [a, b, c]),
e = lists:nth(2, [d, e, f]),
i = lists:nth(3, [g, h, i]),
?WAS_CALLED(lists:nth(1, [a, b, c]), once),
?WAS_CALLED(lists:nth(2, [d, e, f]), once),
?WAS_CALLED(lists:nth(3, [g, h, i]), once),
?FORGET_CALLS(),
?WAS_CALLED(lists:nth(1, [a, b, c]), never),
?WAS_CALLED(lists:nth(2, [d, e, f]), never),
?WAS_CALLED(lists:nth(3, [g, h, i]), never),
Generated by EDoc