Copyright © (C) 2011-2021 Sven Heyll
Behaviours: gen_statem.
Authors: Sven Heyll (sven.heyll@gmail.com).
The module name 'em' stands for 'Erly Mock'.
This mocking library works similar to Easymock.
After a mock process is started by new/0
it can be programmed to
expect function calls and to react to them in two ways:
strict/4
, strict/5
, stub/4
, stub/5
Before the code under test is executed, the mock must be told
that the programming phase is over by replay/1
.
In the next phase the code under test is run, and might or might not call
the functions mocked. The mock process checks that all functions programmed
with strict/4
, strict/5
are called in the correct order,
with the expected arguments and reacts in the way defined during the
programming phase. If a mocked function is called although another function
was expected, or if an expected function was called with different
arguments, the mock process dies and prints a comprehensive error message
before failing the test.
To support mock invokations from multiple processes the strictness
requirement can be reduced to calls belonging to the same group. new_groups/2
creates a list of named groups, where calls belongig to
different groups may occur in any order. A group is passed as mock reference
(1st parameter) to strict/5
or strict/4
. Use await/1
with a list of groups to block the caller until all groups
are finished, i.e. the expectations assigned to each group via strict/5
were invoked. NOTE: It is prohibited to use the same expectations
with different return values among a list groups created together.
At the end of a unit test await_expectations/1
is called to
await all invocations defined during the programming phase.
An alternative to await_expectations/1
is verify/1
. It is
called to check for missing invocations at the end of the programming phase,
if any expected invocations are missing at verify will throw an
exception.
When the mock process exits it tries hard to remove all modules, that were dynamically created and loaded during the programming phase.
NOTE: This library works by purging the modules mocked and replacing them with dynamically created and compiled code, so be careful what you mock, i.e. it brings chaos to mock modules from kernel. This also implies, that tests that mock the same modules must be run sequentially.
Apart from that, it is very advisable to only mock owned modules anyway.
Also, mocking modules is an operation that mutates the whole erlang
virtual machine (more or less), therefore it is advisable to call
lock/0
and unlock/0
around a block
test code that relies on an intact set of modules, which are not
mocked.
em
is made sequential through a single
em_module_loader
server.
answer() = {function, fun(([any()]) -> any())} | {return, any()}
args() = [fun((any()) -> true | false) | term()]
group() = {group, pid(), group_tag()}
group_tag() = {term(), reference()}
timeout_millis() = non_neg_integer() | infinity
any/0 | Utility function that can be used as a match function in an argument list to match any value. |
await/2 |
Block until a specific invokation defined via strict/4 during the
programming phase was made. |
await_expectations/1 | Wait until all invokations defined during the programming phase were made. |
call_log/1 | Retrieve a list of successfully mocked invokations, i.e. |
lock/0 | |
new/0 | Spawn a linked mock process and return the pid. |
new_groups/2 | Create a group handle to assign mock expectation to. |
nothing/2 | This is used to express the expectation that no function of a certain module is called. |
replay/1 | Finishes the programming phase and switches to the replay phase where the actual code under test may run and invoke the functions mocked. |
replay/2 |
Finishes the programming phase and switches to the replay phase, expecting
that invokations are recorded at least once every InvokationTimeout millis. |
strict/4 | Add an expectation during the programming phase for a specific function invokation. |
strict/5 |
This function behaves like strict/4
and additionally accepts a return value or an answer function. |
stub/4 | Defines a what happens when a function is called whithout recording any expectations. |
stub/5 |
This is similar stub/4 except that it, like
strict/5 allows the definition of a return value
or an answer function. |
unlock/0 |
Release lock obtained by lock() . |
verify/1 | Finishes the replay phase. |
zelf/0 |
Utility function that can be used as a match function in an
argument list to match self() , e.g. |
any() -> fun((any()) -> true)
Utility function that can be used as a match function in an argument list to match any value.
await(X1::group(), Handle::reference()) -> {success, InvPid::pid(), Args::[term()]} | {error, term()}
Block until a specific invokation defined via strict/4
during the
programming phase was made.
The handle for the specific invokation is the
value returned by strict/4
.
The return value contains the parameters and the pid of the recorded invokation. This function maybe called anytime before or after the referenced invokation has actually happened.
If the handle is not valid, an error is returned.
await_expectations(X1::group()) -> ok
Wait until all invokations defined during the programming phase were made. After this functions returns, the mock can be expected to exit and clean up all modules installed.
call_log(X1::group()) -> [{Mod::atom(), Func::atom(), Args::[term()], Answer::term()}]
Retrieve a list of successfully mocked invokations, i.e. all calls that were
accepted by the em
process in the replay
phase. Both strict and stub
invokations are recorded. NOTE: The Answer might as well be a function,
depending on the return
argument passed to strict
or stub
.
lock() -> ok
new() -> group()
Spawn a linked mock process and return the pid.
This is usually the first thing to do in each unit test. The resulting pid is used in the other functions below.
NOTE: only a single mock proccess is required for a single unit test case. One mock process can mock an arbitrary number of different modules.
When the mock process dies, all uploaded modules are purged from the code server, and all cover compiled modules are restored.
When the process that started the mock exits, the mock automatically cleans up and exits.
After new() the mock is in 'programming' state.
Create a group handle to assign mock expectation to. The result can be passed
to strict/4
or strict/5
and await/2
.
nothing(X1::group(), Mod::atom()) -> ok
This is used to express the expectation that no function of a certain module is called. This will cause each function call on a module to throw an 'undef' exception.
replay(G::group()) -> ok
Finishes the programming phase and switches to the replay phase where the actual code under test may run and invoke the functions mocked. This may be called only once, and only in the programming phase. This also loads (or replaces) the modules of the functions mocked. In the replay phase the code under test may call all mocked functions. If the application calls a mocked function with invalid arguments, or if the application calls a function not expected on a mocked module, the mock process dies and - if used in a typical edoc test suite - fails the test.
replay(X1::group(), InvokationTimeout::timeout_millis()) -> ok
Finishes the programming phase and switches to the replay phase, expecting
that invokations are recorded at least once every InvokationTimeout
millis.
See also: replay/1.
Add an expectation during the programming phase for a specific function invokation.
All expectations defined by 'strict' define an order in which the application must call the mocked functions, hence the name 'strict' as oposed to 'stub' (see below).
The parameters are:
M
the mock pid, returned by new/0
Mod
the module of the function to mockFun
the name of the function to mockArgs
a list of expected arguments.
Each list element is either a value that will be matched to the actual value
of the parameter at that position, or a predicate function which will be
applied to the actual argument.This function returns a reference that identifies the expectation. This
reference can be passed to await/2
which blocks until the expected
invokation happens.
The return value, that the application will get when calling the mocked
function in the replay phase is simply the atom ok
. This
differentiates this function from strict/5
, which allows the
definition of a custom response function or a custom return value.
new/0
and replay/1
- that is during the programming phase.
This function behaves like strict/4
and additionally accepts a return value or an answer function. That parameter
Answer
may be:
{return, SomeValue}
This causes the mocked function invocation to
return the specified value.{function, fun(([Arg1, ... , ArgN]) -> SomeValue)}
This defines
a function to be called when the mocked invokation happens.
That function is applied to all captured actual arguments. For convenience these
are passed as a list, so the user can simply write fun(_) -> ...
when the actual values are not needed.
The function will be executed by the process that calls the mocked function, not
by the mock process. Hence the function may access self()
and may
throw an exception, which will then correctly appear in the process under test,
allowing unit testing of exception handling.
Otherwise the value returned by the function is passed through as the value
returned from the invocation.
Defines a what happens when a function is called whithout recording any expectations. The invocations defined by this function may happen in any order any number of times. The way, the invocation is defined is analog to
See also: strict/4
.
This is similar stub/4
except that it, like
strict/5
allows the definition of a return value
or an answer function.
unlock() -> ok
Release lock obtained by lock()
.
verify(X1::group()) -> ok
Finishes the replay phase. If the code under test did not cause all expected
invokations defined by strict/4
or strict/5
, the
call will fail with badmatch
with a comprehensive error message.
Otherwise the mock process exits normally, returning ok
.
zelf() -> atom()
Utility function that can be used as a match function in an
argument list to match self()
, e.g. when it matches the pid of the
process, that calls the funtion during the replay phase.
Generated by EDoc