Help us understand the problem. What is going on with this article?

meck (Erlang mockライブラリ) の使い方

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 kernelstdlib modulemockしたい場合に付ける
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().

args_spec()参照

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).

args_spec()参照

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

args_spec()参照

その他

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()(返値を直接指定) が可能

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした