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

# meck (Erlang mockライブラリ) の使い方
Erlangのmockライブラリ 「[meck](https://github.com/eproxus/meck)」
基本的な使い方を日本語でまとめてある記事もあったが、細かいオプションが分からず、使っていて詰まったので勉強がてらソースコードと本家のドキュメントを読みつつ纏めてみた。


レポジトリ|[eproxus/meck](https://github.com/eproxus/meck)|
---|---|
バージョン|0.8.2|
ライセンス| Apache License v2|

## 注意
何故か`lists`で例を書いていますが, `lists`はmeckできませんので悪しからず...
同様に`stdlib`の中にはmeckできないモジュールがいくつかあります.

## 型
何回も出てくるので、はじめにいくつか定義をしておく。

### 基本
```erlang
%% モジュール名
-type module()   :: atom().
%% 関数名
-type func_name():: atom(). 
%% lists:reverse/1, lists:nth/2 などの/1, /2の部分
-type arity()    :: pos_integer().
```

### その他
- [matcher()](#matcher)
- [args_spec()](#arg_spec)
- [ret_spec()](#ret_spec)


## setup/teardown
### new
```erlang
% 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()](#ret_spec)}|従来の`module`では定義されているが、`mock`されていない関数の返り値を指定する|
|`stub_all`|`{stub_all, ok}`と等価|

*補足*

- Erlang shell上で`c(file)`. でコンパイルした物では`passthrough`がうまく動作せず手間取った

### unload
```erlang
% 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
```erlang
%% 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()](#args_spec) および [ret_spec()](#ret_spec) 参照

補足：`meck:new`を呼ばなくても`expect`することができる. その際は`[passthrough]`を指定して自動的に`new`される.

### sequence
```erlang
%% 呼びだされた順番によって返り値を変更したい
-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
```erlang
%% 返り値を一定周期で繰り返したい
-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
```erlang
%% expectを取り消す
-spec meck:delete(module() | [module()], func_name(), arity()) -> ok.
```
### exception
```erlang
%% exceptionを返す
%% throw(Class, Reason)
%% meck:expectのfunにのみ使用可能
-spec meck:exception(Class :: throw | error | exit, Reason :: any()) -> no_return().
```
### passthrough
```erlang
%% originalの関数を呼び出す
%% meck:expectのfunにのみ使用可能
-spec meck:passthrough(Args) -> Result when
    Args   :: [any()],
    Result :: any(). 
```

## Check API

### validate
```erlang
%% 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
```erlang
-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
```erlang
%% 指定回数目に呼ばれた際の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()参照](#args_spec)

### num_calls/called
```erlang
%% 関数が呼ばれた回数を取得する
-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()参照](#args_spec)

### wait
```erlang
%% 該当する関数の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()参照](#args_spec)

## その他
### matcher<a name="matcher">
matchするかを返す関数を指定する事ができる

```erlang
%% 真の場合は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<a name="arg_spec">
引数が何の時の動作かを指定する

```erlang
-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<a name="ret_spec">
`expect function`内で以下の表記が使用できる。

```erlang
%% 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()](#matcher), `any()`(返値を直接指定)　が可能