Posted on Thursday, 9th October 2008 by charpi
In my previous post, I described a way to mock module by replacing
them. Today, I’ll post a version closer to other mock
library. Basically, I create on mock module on the fly and forward all
calls to a process. You can tell to this process how to answer for
each calls.
Here is the test :
-module(mock_test).
-export([test /0]).
test () ->
lazy_way () ,
dynamic_way (),
ok.
lazy_way () ->
"i'm the production code" = my_module: who(),
mock: replace_module (my_module, my_mock_module),
"I'm the mocked code" = my_module: who (),
mock: uninstall (my_module),
"i'm the production code" = my_module: who (),
ok.
dynamic_way () ->
"i'm the production code" = my_module: who (),
{pong,host} = my_module: ping (host),
mock: start (),
mock: add_module(my_module),
mock: set_answer (my_module, who, "I'm the mocked code"),
"I'm the mocked code" = my_module: who (),
mock: set_answer (my_module, who, "Oops did it wrong"),
"Oops did it wrong" = my_module: who (),
[{my_module, who, []}, {my_module, who, []}] = mock: calls (),
[] = mock: calls (),
case catch my_module: ping(host) of
error_no_response -> ok;
Other -> exit({unexepected_mock_response, Other})
end,
mock: set_answer (my_module, ping, {pang, mock_host}),
{pang, mock_host} = my_module: ping(last_host),
[{my_module, ping, [host]},
{my_module, ping, [last_host]}] = mock: calls (),
mock: uninstall (my_module),
"i'm the production code" = my_module: who (),
{pong,host} = my_module: ping (host),
ok.
and the implementation
-module(mock).
-export([replace_module /2]).
-export([uninstall /1]).
-export([start /0]).
-export([add_module /1]).
-export([set_answer /3]).
-export([calls /0]).
replace_module (Module, Mock_module) ->
uninstall (Module),
{ok, Binary} = file: read_file (code: which (Mock_module)),
File_name = atom_to_list (Module) ++ ".erl",
code: load_binary(Module, File_name, Binary),
ok.
uninstall (Module) ->
code: purge (Module),
code: delete (Module).
start () ->
Pid = spawn_link (fun () -> mocker ([],[]) end),
register(mocker, Pid),
ok.
add_module (Module) ->
Forms = forms (Module),
uninstall (Module),
{ok, _, Binary} = compile: forms(Forms, [report]),
code: load_binary (Module, "foo.erl", Binary),
ok.
set_answer (Module, Function, Answer) ->
mocker ! {set_answer, Module, Function, Answer},
ok.
calls () ->
mocker ! {self(), calls},
receive
{calls, Calls} ->
Calls
end.
forms (Module) ->
Exported_functions = find_exported_functions (Module),
Fun = fun (F) -> function_to_form (Module, F) end,
Functions_forms = [Fun(F) || F <- Exported_functions],
[{attribute,1,module,Module},
{attribute,3,export,Exported_functions}] ++
Functions_forms ++
[ {function,16,wait_response,0,
[{clause,16,[],[],
[{'receive',20,
[{clause,20,
[{tuple,20,[{atom,20,response},{atom, 20, undefined}]}],
[],
[{call,20,
{atom,20,throw},
[{atom,20,error_no_response}]}]},
{clause,20,
[{tuple,20,[{atom,20,response},{var,20,'Response'}]}],
[],
[{var,19,'Response'}]}
]}]}]},
{eof,23}].
function_to_form (Module, {Function, Arity}) ->
Parameters = parameters (Arity),
Parameters_cons = parameters_cons (Arity),
{function,5,Function,Arity,
[{clause,5,Parameters,[],
[{op,6,'!',
{atom,6,mocker},
{tuple,6,
[{call,6,{atom,6,self},[]},
{atom,6,forward},
{atom,6,Module},
{atom,6,Function},
Parameters_cons]}},
{call,8,{atom,8,wait_response},[]}
]}]}.
parameters (0) ->
[];
parameters (N) ->
Seq = lists: seq (1,N),
F = fun (I) ->
String = lists:flatten (io_lib: format ("Var~p",[I])),
list_to_atom(String)
end,
[{var, 6, F(I)} || I <- Seq].
parameters_cons (N) ->
parameter_list_form (parameters (N)).
put_parameter_in_call (Parameters) ->
list_to_tuple (transform_list_to_cons (Parameters)).
transform_list_to_cons ([]) ->
[nil, 6];
transform_list_to_cons ([H|T]) ->
[cons, 6, H, put_parameter_in_call (T)].
parameter_list_form ([]) ->
{nil,6};
parameter_list_form (Variable_forms) ->
put_parameter_in_call (Variable_forms).
find_exported_functions (Module) ->
Module_info = Module: module_info (),
All_exported = proplists: get_value (exports, Module_info),
lists: filter (fun ({module_info,_}) ->
false;
(_) ->
true
end, All_exported).
mocker (Modules, Calls) ->
receive
{From, calls} ->
From ! {calls,lists: reverse (Calls)},
mocker (Modules, []);
{set_answer, Module, Function, Answer} ->
New_modules = proplists: delete ({Module, Function}, Modules),
mocker ([{{Module, Function}, Answer}|New_modules], Calls);
{From, forward, Module, Function, Args} ->
Response = proplists: get_value ({Module,Function}, Modules),
From ! {response, Response},
mocker (Modules, [{Module, Function, Args}|Calls])
end.
Tags: erlang, mock object
Posted in Uncategorized | Comments (3)