2. What’s ErlyMock?
A mocking framework for Erlang
Mock any method in any modules
Various features:
Argument verification
Call order verification
Divergence:
Sheyll’s: http://erlymock-site.sourceforge.net
Nialscorva’s:
https://github.com/nialscorva/erlymock
3. Looking down from 30,000ft
high
Sheyll’s version:
M = em:new(),
em:strict(M, module, function, [arg, em:any()], {return, ok}),
em:stub(M, module, function2, [1, 2, 3], {function, fun (_) -> throw(myerror)
end}),
% replaying phase
em:replay(M),
% test
?assertMatches(ok, module:function(arg, anything)),
?assertThrow(myerror, module:function2(1, 2, 3)),
em:verify(M).
4. Looking down from 30,000ft
high
Nialscorva version:
erlymock:start(),
erlymock:strict(module, function, [arg, '_'], [{return, ok}]),
erlymock:stub(module, function2, [1, 2, 3], [{throw, myerror}]),
% initialize
erlymock:replay(),
% test
?assertMatches(ok, module:function(arg, anything)),
?assertThrow(myerror, module:function2(1, 2, 3)),
erlymock:verify().
5. ErlyMock(sheyll’s) Funcs
em:new/0 – Create new mock expectation instance (gen_fsm proc)
em:strict/4,5 – Strict call expectation
Strict calls must occur once for each, and in the order of declarations.
em:strict/4 mocked func always return atom ‘ok’
em:strict/5 can specify return value with:
{return, V} – Mocked func return value V
{function, F} – Mocked func call function F, and use its return value as return value
em:stub/4,5 – Stub call expectation
Stub calls can occur any times, and in any order.
Mocked func return value can be specified as above.
em:replay/1 – Finish expectation programming phase, start replaying phase
Must be called before actual test code and after expectation declarataions.
em:verify/1 – Verify expectations, and destroy mock instance
Must be called after test code.
em:any/0 – Return wildcard argument verifier
em:nothing/2 – Demand no funcs in the specified module can be called
6. ErlyMock(nialscorva’s) Funcs
erlymock:start/0 – Start mock service (gen_server proc)
erlymock:strict/3,4 – Strict call expectation
Strict calls must occur once for each, and in the order of declarations.
erlymock:strict/3 mocked funcs always return atom ‘ok’
erlymock:strict/4 can specify return value with (put in a list):
{return, V} – Mocked func return value V
{throw, V} – Mocked func call erlang:throw/1 with reason V
{exit, V} – Mocked func call erlang:exit/1 with reason V
{error, V} – Mocked func call erlang:error/1 with reason V
{function, F} – Mocked func call function F, and use its return value as return value
erlymock:replay/0 – Finish expectation programming, start replaying phase
Must be called before actual test code and after expectation declarataions.
erlymock:verify/0,1 – Verify expectations, and destroy mock instance
Must be called after test code.
erlymock:verify/1 can specify timeout for verification process.
7. ErlyMock(nialscorva’s) Funcs,
cont.
erlymock:stub/3,4 – Stub call expectation
Stub calls can occur any times, and in any order.
Mocked func return value can be specified as above.
Can retrict minimum and maximum invocation times of
mocked func with options:
{min_invocations, Count} – Specify min invocation times,
default to 0.
{max_invocations, Count} – Specify max invocation times,
default to infinity.
erlymock:o_o/3,4 – Out of order call expectation
Out of order calls are simply stub calls with
min_invocation=max_invocation=1, i.e. call exactly once in
any order.
Mocked func return value can be specified as above.
8. Mock example – Missile
Launcher
Two layer:
launch_console– Interact with missile operator
launcher – Called by console, control missile
hardware
Need test launch_console:launch/0, it must comply to
the following restrictions:
Must call launcher:confirm/0 first
If launcher:confirm/0 return false, nothing to do
If launcher:confirm/0 return true, call launcher:launch/2
with fixed coordinate
Time must be retrieved through launcher:time/0
9. Mock example - Missile
Launcher
Mocking test cases:
mock_test1() ->
M = em:new(),
em:stub(M, launcher, time, [], {function, fun () -> Old = case get(mock_time) of undefined -> 0; V -> V end, put(mock_time, Old+1), Old
end}),
em:stub(M, launcher, launch, [em:any(), em:any()], {function, fun ()->throw(should_not_happen) end}),
em:strict(M, launcher, confirm, [], {return, false}),
em:replay(M),
launch_console:launch(),
em:verify(M).
mock_test2() ->
Lat = 33.8, Lon = 45.0,
M = em:new(),
em:stub(M, launcher, time, [], {function, fun () -> Old = case get(mock_time) of undefined -> 0; V -> V end, put(mock_time, Old+1), Old
end}),
em:strict(M, launcher, confirm, [], {return, true}),
em:strict(M, launcher, launch, [Lat, Lon]),
em:replay(M),
launch_console:launch(),
em:verify(M).
10. Commons between impls
Use beam code hot swapping mechanism to
implement module mocking.
See compile:forms/1, code:purge/1, code:delete/1 and
erlang:load_module/2
Note:
Mocking must happened for entire module, restricted by
this impl method. Mocking partial funcs in a module is
netiher possible nor desirable.
Can’t mock the same module in parallel running test cases.
Mocked funcs are generated on the fly in memory,
according to declared expectations.
See erl_syntax module
11. Sheyll’s compare to
Nialscorva’s
Pros
Can recover coverage data after mocking finished
Shortter module name ;-)
Support multiple expectation instance (can parallel run test cases if using
different mock module)
Implemented in gen_fsm, cleaner than Nialscorva’s
With ‘nothing’ expectation
With ‘any’ arg verifier, express wildcard matching in cleaner way
Cons
Can’t restrict stub call min/max invocation times
No out of order call expectation (due to above reason)
No express way to specify throw/exit/error return value
No built-in TCP server mocking support (can be done with self-connected socket
pair)
No short-cut wildcard arg verifier ‘_’ (but also without its ambiguity)
Use maven instead of rebar as building tools, not very erly…
12. Personal thoughts
Neither impl are good enough…
For now, recommend Sheyll’s impl prior to
Nialscorva’s
We can contribute our efforts to make Sheyll’s
impl better, it’s not so hard.
Sheyll’simpl: 641 lines
Nialscorva’s impl: 843 lines