Testing multi-process erlang gen_servers can be tricky. Typically one relies simply on pattern matching to verify that the response matches what you would expect.
{expected,Response}=gen_server:call(Pid, hi).
As long as the gen_server call hi returns expected as the first element of the tuple, then the tests pass.
The technique is also the same when building client-server code where both client and server are gen_servers. The common case is to simply test one side at a time; test the response of all client calls and then (independently) test the responses of the server calls.
What about asynchronous cast?
gen_server:call is convenient because it is synchronous and returns a value. gen_server:cast, on the other hand, is asynchronous and always returns the atom ok. This can make casts difficult to test.
gen_server_mock is a library to mock gen_server processes that expect specific, ordered sets of messages. It allows you to unit test gen_servers by verifying they are receiving the expected set of messages.
Example 1
{ok,Mock}= gen_server_mock:new(),
gen_server_mock:expect(Mock, call,fun({foo, hi},_From,_State)->okend),
gen_server_mock:expect_call(Mock,fun({bar, bye},_From,_State)->okend),
ok =gen_server:call(Mock,{foo, hi}),
ok =gen_server:call(Mock,{bar, bye}),
ok = gen_server_mock:assert_expectations(Mock)
This Mock expects two calls: {foo, hi} and {bar, bye}. Since Mock receives both of these messages, assert_expectations does not raise any errors.
{ok,Mock}= gen_server_mock:new(),
gen_server_mock:expect_call(Mock,fun(one,_From,_State)->okend),
gen_server_mock:expect_call(Mock,fun(two,_From,State)->{ok,State}end),
gen_server_mock:expect_call(Mock,fun(three,_From,State)->{ok, good,State}end),
gen_server_mock:expect_call(Mock,fun({echo,Response},_From,State)->{ok,Response,State}end),
gen_server_mock:expect_cast(Mock,fun(fish,State)->{ok,State}end),
gen_server_mock:expect_info(Mock,fun(cat,State)->{ok,State}end),
ok =gen_server:call(Mock, one),
ok =gen_server:call(Mock, two),
good =gen_server:call(Mock, three),
tree =gen_server:call(Mock,{echo, tree}),
ok =gen_server:cast(Mock, fish),Mock! cat,
gen_server_mock:assert_expectations(Mock)
Currently three types of messages are supported: call, cast, and info.
The signature of the fun of each expectation is the same as the corresponding gen_server:handle_*. So the fun for expect_call has the same signature as handle_call: fun(Request, From, State). See man gen_server for more information.
However, the return value of the funmust be one of:
ok |
{ok,NewState} |
{ok,ResponseValue,NewState} |
Anything else will be an error. Note that you can change the state of your Mock by returning NewState.
Arbitrary, non-gen_server messages are handled with expect_info, e.g. Mock ! cat fulfills the expect_info in the example above.
testing erlang gen_server with gen_server_mock
Testing by synchronous pattern matching
Testing multi-process erlang gen_servers can be tricky. Typically one relies simply on pattern matching to verify that the response matches what you would expect.
As long as the
gen_servercallhireturnsexpectedas the first element of the tuple, then the tests pass.The technique is also the same when building client-server code where both client and server are
gen_servers. The common case is to simply test one side at a time; test the response of all client calls and then (independently) test the responses of the server calls.What about asynchronous
cast?gen_server:callis convenient because it is synchronous and returns a value.gen_server:cast, on the other hand, is asynchronous and always returns the atomok. This can makecasts difficult to test.gen_server_mockis a library to mockgen_serverprocesses that expect specific, ordered sets of messages. It allows you to unit testgen_servers by verifying they are receiving the expected set of messages.Example 1
This
Mockexpects twocalls:{foo, hi}and{bar, bye}. SinceMockreceives both of these messages,assert_expectationsdoes not raise any errors.What is verified
gen_server_mock:assert_expectations(Mock)verifies that:You can catch the
exitby using the following:Example 2
Currently three types of messages are supported:
call,cast, andinfo.The signature of the
funof each expectation is the same as the correspondinggen_server:handle_*. So thefunforexpect_callhas the same signature ashandle_call:fun(Request, From, State). Seeman gen_serverfor more information.However, the return value of the
funmust be one of:ok | {ok, NewState} | {ok, ResponseValue, NewState} |Anything else will be an error. Note that you can change the state of your
Mockby returningNewState.Arbitrary, non-
gen_servermessages are handled withexpect_info, e.g.Mock ! catfulfills theexpect_infoin the example above.References