meck (Erlang mockライブラリ) の使い方
Erlangのmockライブラリ 「meck」
基本的な使い方を日本語でまとめてある記事もあったが、細かいオプションが分からず、使っていて詰まったので勉強がてらソースコードと本家のドキュメントを読みつつ纏めてみた。
レポジトリ | eproxus/meck |
---|---|
バージョン | 0.8.2 |
ライセンス | Apache License v2 |
注意
何故かlists
で例を書いていますが, lists
はmeckできませんので悪しからず...
同様にstdlib
の中にはmeckできないモジュールがいくつかあります.
型
何回も出てくるので、はじめにいくつか定義をしておく。
基本
%% モジュール名
-type module() :: atom().
%% 関数名
-type func_name():: atom().
%% lists:reverse/1, lists:nth/2 などの/1, /2の部分
-type arity() :: pos_integer().
その他
setup/teardown
new
% mockするmoduleを指定する。リストで指定可能
% erlangなどOTP関係でmockできないものあり (errorが投げられる)
-spec meck:new(module() | [module()]) -> ok.
%% Optionを指定可能
-spec meck:new(module() | [module()], [Option]) -> ok.
オプション | 概要 |
---|---|
passthrough |
mock されていない関数は、従来の物を使用する。stub_allと併用不可 |
no_link |
呼び出し元のプロセスにリンクしない (not start_link ) |
unstick |
kernel やstdlib module をmock したい場合に付ける |
no_passthrough_cover |
passthrough した際に、カバレッジの対象外にする |
no_history |
meck が呼ばれた履歴を保持しない |
non_strict |
ロードしていないmodule に大してmock したい場合に付ける |
{spawn_opt, list()} |
{spawn_opt, [link]} しか指定できないと思われる. このオプションを使う必要はない. |
{stub_all, ret_spec()} | 従来のmodule では定義されているが、mock されていない関数の返り値を指定する |
stub_all |
{stub_all, ok} と等価 |
補足
- Erlang shell上で
c(file)
. でコンパイルした物ではpassthrough
がうまく動作せず手間取った
unload
% newしたmoduleを一括してunloadする
-spec meck:unload() -> [module()].
% newしたmoduleのうち、指定したmoduleのみunloadする
% newしていないmoduleを指定すると error({not_mocked, Module}) が投げられる
-spec meck:unload(module() | [module()]) -> ok.
Mock API
expect
%% meck:expect/3
%% Expectationはリストで指定できるが、異なるarityを指定してはならない
-spec meck:expect(module() | [module()], func_name(), Expectation) -> ok when
Expectation :: fun() | [{arg_spec(), ret_spec()}].
%% meck:expect/4
-spec meck:expect(module() | [module()], func_name(), args_spec(), ret_spec()) -> ok.
%% e.g.
%% lists:reverse(_) -> [3,2,1]
meck:expect(lists, reverse, fun(_) -> [3,2,1] end).
%% lists:reverse(1,2) -> [2,1]; (1,_) -> [3,2,1]; (_, _) -> [c,b,a].
meck:expect(lists, reverse, [{[1,2], [2,1]},
{[1, '_'], [3,2,1]},
{2, [c,b,a]}]). % arity指定
%% lists:reverse(_, a, [_|_]) -> [c,b,a]
meck:expect(lists, reverse, ['_', a, ['_'|'_']], [c,b,a]).
%% lists:reverse() -> [9,8,7]
meck:expect(lists, reverse, 0, [9,8,7]).
args_spec() および ret_spec() 参照
補足:meck:new
を呼ばなくてもexpect
することができる. その際は[passthrough]
を指定して自動的にnew
される.
sequence
%% 呼びだされた順番によって返り値を変更したい
-spec sequence(module() | [module()], func_name(), arity(), Values()) -> ok when
Values :: [any()].
%% e.g.
1> meck:sequence(lists, reverse, 1, [ [3,2,1], [c,b,a] ]).
ok
2> lists:reverse(a).
[3,2,1]
3> lists:reverse(a).
[c,b,a]
4> lists:reverse(a). %% 以降同じ
[c,b,a]
loop
%% 返り値を一定周期で繰り返したい
-spec meck:loop(module() | [module()], func_name(), arity(), Values) -> ok when
Values :: [any()].
%% e.g.
1> meck:loop(lists, reverse, 1, [ [3,2,1], [c,b,a] ]).
ok
2> lists:reverse(a).
[3,2,1]
3> lists:reverse(a).
[c,b,a]
4> lists:reverse(a).
[3,2,1]
delete
%% expectを取り消す
-spec meck:delete(module() | [module()], func_name(), arity()) -> ok.
exception
%% exceptionを返す
%% throw(Class, Reason)
%% meck:expectのfunにのみ使用可能
-spec meck:exception(Class :: throw | error | exit, Reason :: any()) -> no_return().
passthrough
%% originalの関数を呼び出す
%% meck:expectのfunにのみ使用可能
-spec meck:passthrough(Args) -> Result when
Args :: [any()],
Result :: any().
Check API
validate
%% moduleがmeckされている場合はtrue
%% exceptionによってmeckが壊れた場合はfalse
%% そもそもmeckされていない物はerror({not_mecked, _})
-spec meck:validate(module() | [module()]) -> boolean().
%% e.g.
1> meck:new(aaa, [non_strict]).
ok
2> meck:validate(aaa).
true
3> meck:validate(bbb).
*** exception error: {not_mocked, bbb} % falseではない
4> meck:new(ccc, [non_strict]).
ok
5> catch ccc:not_exist().
{'EXIT', {function_clause, [{...}]}}
6> meck:validate(ccc).
false
history/reset
-spec history() :: [
{pid(), {module(), func_name(), Args :: [any()]}, Result :: any()} |
{pid(), {module(), func_name(), Class :: throw|error|exit, Reason::any(), stack_trace()}
].
%% 指定したモジュールのmockが呼ばれた履歴を取得する
-spec meck:history(module()) -> history().
-spec meck:history(module(), pid() | '_') -> history().
%% historyをリセットする
-spec meck:reset(module() | [module()]) -> ok.
capture
%% 指定回数目に呼ばれた際のArgNum番目の引数を取得する
%% pid()を省略した meck:capture/5もある
-spec meck:capture(Occur, module(), func_name(), OptArgsSpec, ArgNum, OptCallerPid) -> Result when
Occur :: first | last | Nth,
Nth :: pos_integer(),
OptArgsSpec :: '_' | args_spec(),
OptCallerPid:: '_' | pid(),
ArgNum :: pos_integer(),
Result :: any().
num_calls/called
%% 関数が呼ばれた回数を取得する
-spec meck:num_calls(module(), OptFun, OptArgsSpec) -> non_neg_integer() when
OptFun :: '_' | func_name(),
OptArgsSpec :: '_' | args_spec().
%% lists:reverse/0
meck:num_calls(lists, reverse, 0).
%% self()がlistsの関数を呼んだ回数
meck:num_calls(lists, '_', '_', self()).
%% Argsを指定する
meck:num_calls(lists, reverse, [1,2,3]).
%% 細かくArgs判定を行う
%% lists:reverse(_, 2, [_|_]) の回数
meck:num_calls(lists, reverse, ['_', 2, ['_' | '_']]).
%% 真偽値のみが欲しい場合
%% 引数はnum_callsと同じ
meck:called(lists, reverse, 0).
wait
%% 該当する関数のhistoryが一定数 (Times) 以上になるまで待つ
%% historyの数なので, meck:reset/1 を呼ばない限り結果は変わらない
-spec wait(module(), OptFunc, OptArgsSpec, Timeout) -> ok when
OptFunc :: func_name(),
OptArgSpec :: '_' | func_name(),
OptArgsSpec :: '_' | args_spec(),
Timeout :: non_neg_integer(). % ms
-spec wait(Times, module(), OptFunc, OptArgsSpec, Timeout) -> ok when
Times :: non_neg_integer(),
OptFunc :: func_name(),
OptArgSpec :: '_' | func_name(),
OptArgsSpec :: '_' | args_spec(),
Timeout :: non_neg_integer(). % ms
-spec wait(Times, module(), OptFunc, OptArgsSpec, OptCallerPid, Timeout) -> ok when
Times :: non_neg_integer(),
OptFunc :: func_name(),
OptArgSpec :: '_' | func_name(),
OptArgsSpec :: '_' | args_spec(),
OptCallerPid:: '_' | pid(),
Timeout :: non_neg_integer(). % ms
その他
matcher
matchするかを返す関数を指定する事ができる
%% 真の場合はtrue, 偽の場合はそれ以外を返す関数を引数に取る
-spec meck:is(fun( (any()) -> true | any() )) -> matcher().
1> meck:is( fun(X) -> case X rem 2 of 0 -> true; _ -> X end ).
args_spec
引数が何の時の動作かを指定する
-type args_spec() :: [any() | '_' | matcher()] | arity().
%% 参考
-spec meck:expect(module(), func_name(), args_spec(), ret_spec()) -> ok.
%% arity()の指定
meck:expect(module, hoge, 1, meck:val(1)). % module:hoge/1
meck:expect(module, hoge, ['_', '_'], meck:val(1)). % module:hoge/2
meck:expect(module, hoge, [1, 2], meck:val(1)). % module:hoge(1, 2)
meck:expect(module, hoge, ['_', 2, ['_' | '_']], meck:val(1)). % module:hoge(_, 2, [_ | _])
meck:expect(module, hoge, ['_', meck:is(fun(X) -> X =:= 2 end)], meck:val(1)). % module:hoge(_, 2)
ret_spec
expect function
内で以下の表記が使用できる。
%% meck:loop/4 参照
-spec meck:loop([ret_spec()]) -> ret_spec().
%% meck:sequence/4 参照
-spec meck:seq([ret_spec()]) -> ret_spec().
%% 値を指定する
%% meck:val(2) と 2 は等価
-spec meck:val(any()) -> ret_spec().
%% meck:exception/2 参照
-spec meck:raise(Class, Reason) -> ret_spec when
Class :: throw | error | exit,
Reason :: term().
%% meck:passthrough/1 参照
-spec meck:passthrough() -> ret_spec().
%% 引数の関数を実行する
-spec meck:exec(fun()) -> ret_spec().
%% e.g.
1> meck:expect(hoge, aaa, [{1, meck:exec(fun(X) -> X * 2 end)}]).
ok
2> hoge:aaa(1).
2
%% arityと引数の数は等しくないとならない
3> meck:expect(hoge, aaa, [{2, meck:exec(fun() -> ok end)}]).
ok
4 > hoge:aaa(1, 2).
** {{badarity,{#Fun<erl_eval.20.90072148>,[1]}}, ...
これに加え、mecther(), any()
(返値を直接指定) が可能