よくわかってない人が書いたmockitoの説明
// こんなクラスがあって、これをモックするイメージで説明っぽいのが書いてある。
class Hoge {
String getMessage(String arg) {
System.out.println("呼ばれた!");
return arg + "ほげーん";
}
}
●モックインスタンスを作る
・@mock(Class)
モックインスタンスを取得する。
Hoge h = Mockito.mock(Hoge.class); // って感じで使う。
h.getMessage(); // 作ったばかりだとメソッドを呼んでも何も起きない。呼ばれた!もでない。
・Amock(Class, String)
デバッグ用名称を設定?MockSettingsのnameに指定文字をいれて、
MockSettings指定と同じ動作。
よーわからん。
・Bmock(Class, Answer)
そのクラスのメソッドの挙動がAnswer.answerにすべて置き換わる。(defaultAnswerの設定)
// Answerの実装の仕方は下のようなかんじ。sysアウトで引数が出力されてmockito!が返されるという挙動に置き換わる。
Answer ans = new Answer() {
public String answer(InvocationOnMock inv) {
System.out.println(inv.getArguments()[0]);
return "mockito!";
};
Hoge h = Mockito.mock(Hoge.class, ans);
h.getMessage("あいうえお"); // あいうえお、mockito!が標準出力される。
・Cmock(Class, MockSettings)
Mocksettingsを指定。Mocksettingの中にnameやdefaultAnswerがある。
AとBを同時設定したり色々できる模様。よーわからんけど…。
・D@Mockアノテーション
@Mock
Hoge hoge; //フィールドを作っておいて、
MockitoAnnotations.initMocks(this);
を一度呼び出すとアノテーション設定からモックインスタンスが作られる。
インターフェースもなく継承関係もないクラスの型になんで
モックインスタンスのような不思議なものが入るのか。
CGLIB(コードジェネレーターライブラリー。アスペクトしてどーたらする。)
を使ってメソッドに処理を割りこませているようだ。
ソースをのぞいてみるとEnhancerを使っているし。
(例CGLIBのEnhancer使用例)
public class Hara {
public static void main (String... args) {
Enhancer en = new Enhancer();
en.setSuperclass(Fon.class);
en.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
System.out.println("昇竜");
return "ウメ昇竜";
}
});
// Fonインスタンスのようで実は違うインスタンスをcreate。魔法のようにClassCastExceptionがおきない。
Fon f = (Fon) en.create();
System.out.println(f.hoge());
}
}
class Fon {
public String hoge() {
System.out.println("波動");
return "ウメ波動";
}
}
上記の例は、ウメ波動と表示されるとみせかけてMethodInterceptorのウメ昇竜が割り込んでくる。
これを汎用的にしたのがたぶんMockitoなんだと思う。
●モック設定する。
・OngoingStubbing when(T)
// 例
Hoge h = Mockito.mock(Hoge.class);
Mockito.when(h.getMessage("ヘイ!")).thenReturn("はい!");
// ちなみにvoid戻りのメソッドだけは上記のかきっぷりではモックできない。javaの仕様的に無理。
System.out.println(h.getMessage("ヘイ!"));//はい!が表示
// 引数が一致しないと動作しない。
Hoge h = Mockito.mock(Hoge.class);
Mockito.when(h.getMessage("ヘイ!")).thenReturn("はい!");
System.out.println(h.getMessage("ホイ!"));// nullが表示
// 引数をMatcherにしたり、メソッドチェーンで呼び出し回数に応じたモック設定が可能。
Mockito.when(h.execute(Mockito.anyString()))
.thenReturn("1回目!")
.thenReturn("2回目!")
.thenReturn("3回目!");
System.out.println(h.getMessage("ホイ!"));//1回目!が表示。引数はStringならなんでもOKとするMatcher
System.out.println(h.getMessage("ホイ!"));//2回目!が表示
System.out.println(h.getMessage("ホイ!"));//3回目!が表示
System.out.println(h.getMessage("ホイ!"));//3回目!が表示
// whenの引数はメソッドコールそのものといったイメージなのだが、
// 中をのぞいてみると結構よくわかんない。
// Method型を渡しているのでなく、そのメソッドの戻り値をただ返しているだけに見えるし、
// 実際Mockito.any()の結果はただの空文字っぽい(?)
// Mockito.when(h.getMessage("ヘイ!")).thenReturn("はい!");
// ってやったとき、whenの引数であるmethodCallにはnullが入っていた。
// nullしか渡されてないのにどのメソッドかが識別されている謎。
// っと思ったけどよく考えたらMockitoのモックオブジェクトはすべての作用を記録しているので
// h.getMessageをやったということを内部的に記録していて、
// その直後のthenReturnだからそれに関連付けているという
// ことなのだろうか。
// h.execute(Mockito.anyString());
// Mockito.when(null).thenReturn("あるまげどんばすたー").thenReturn("mockito!");
// ってやってみたらみごと、executeにあるまげどんばすたーとmockito!が設定された。
// やはりそういうことだったらしい。
// whenの引数は実はどうでもいいけどわかりやすいからココに書くっていうしきたりなだけだった。
// Mockito.anyとかも空文字しか返さないけど、多分内部的に別の働きをしていると思われる。こっちは試してない。
●スパイインスタンスを作る
スパイインスタンスは本物のメソッドを呼び出すのがデフォルトになっている
モックインスタンスって感じの存在っぽい。
// スパイインスタンス作成。(本物メソッドを呼ぶ必要があるから本物インスタンスを渡してやる)
Hoge h = Mockito.spy(new Hoge());
Mockito.when(h.getMessage("ヘイ")).thenReturn("ほー");
System.out.println(h.getMessage("ヘイ")); // ほーが表示
System.out.println(h.getMessage("イ")); // 呼ばれた!と、イほげーんが表示(本物の動作)
●スタブインスタンスを作る
Mockito.stub(h.getMessage(Mockito.Any())).toReturn("あ");
System.out.println(h.getMessage("び")); // あ が表示
// モックの挙動と何が違うのかよくわからん...
// そもそもモックとスタブという言葉の意味から調べないとわからんくさい。
// わっかんねーすべてがわっかんねー
●引数検証を行う
スパイインスタンスやモックインスタンスは呼び出し履歴をすべて保持しているため、
実行後にその内容の検証ができる。
・verify(T)
:例1
Hoge h = Mockito.spy(new Hoge());
h.getMessage("はげーん");
Mockito.verify(h).getMessage("ほげーん");//呼び出し引数がほげーんではなかったため、例外が発生する。
:例2
Hoge h = Mockito.spy(new Hoge());
h.getMessage("はげーん");
Mockito.verify(h).getMessage("はげーん");//はげーんで呼ばれてたので問題なく通過する。
・verify(T, VerificationMode)
呼び出し方の確認方法を指定する。
1.VerificationMode times(int)
Mockito.verify(h, Mockito.times(2)).getMessage("はげーん");
// 2回呼ばれていることを確認する。1回でも3回でもダメ。
// 「メソッドが呼ばれた回数」ではなくて
// 「この引数でメソッドが呼ばれた回数」である。
2.VerificationMode never()
Mockito.verify(t, Mockito.never()).getMessage("はげーん");
一度でもこの呼出がされたら例外が発生する。
3.VerificationMode atLeastOnce()
最低1回以上呼ばれることを検証。0回だったら例外が発生する。
4.VerificationMode atLeast(int)
最低n回以上呼ばれることを検証。n-1回以下だったら例外が発生する。
5.VerificationMode atMost(int)
最大n回までの呼び出しであることを検証。n+1回以上呼び出されると例外が発生。
6.VerificationMode only()
Mockito.verify(t, Mockito.only()).getMessage("はげーん");
verifyするメソッド呼び出し以外のパターンの呼び出しが1回でもあればエラー。
上記だとはげーん以外の引数指定があったら例外が起きる。
7.VerificationWithTimeout timeout(int)
タイムアウト時間をミリ秒で指定。それを超えたら例外。
・ArgumentCaptorによる引数検証
verifyの引数検証はequalsで行われるため、それがまずい場合は
ArgumentCaptorで行う。
// でもめんどいんで例はString引数
ArgumentCaptor cap = ArgumentCaptor.forClass(String.class);
Hoge h = Mockito.mock(Hoge.class);
Mockito.when(h.getMessage(cap.capture())).thenReturn("はい!");
h.getMessage("GOGO!");
System.out.println(cap.getValue()); // GOGO!が表示。
// ちなみに@Captor アノテーションをつけたフィールドを作ってもインスタンス作れる。
●リセット
・reset(T...)
Hoge h = Mockito.mock(Hoge.class);
Mockito.when(h.getMessage("あああ")).thenReturn("はい!");
Mockito.reset(h);
System.out.println(h.getMessage("あああ")); // null
Mockito.verify(h, Mockito.never()).getMessage("あああ"); // エラー起きず
モック設定やら何やらがなかったことになる。
これ使うくらいなら新しくテストメソッド作れって公式見解らしい非推奨っぽいメソッド。
●do〜〜メソッド
・void戻りのクラスをモックするときに使用するっぽい。
whenは呼び出し方を書いてから戻りを書くが、
do〜〜は戻りを書いてから呼び出し方を書くという違いがある。
なので戻りを定義して、その戻りに対して呼び出し方を複数定義することができるとか?
・Stubber doThrow(Throwable)
// 指定した例外をスローさせる。
Mockito.doThrow(new RuntimeException()).when(h).execute(Mockito.any());
・Stubber doCallRealMethod()
// 本物メソッドを呼ぶようにする
Mockito.doCallRealMethod().when(h).execute("ほげーん");
// スパイを使わなくても本物呼べる的な?
// どうやって本物インスタンスを取得してるのかは謎。
・Stubber doReturn(Object)
// 使いドコロがよくわかんないけど
// thenReturnと同じ使い方。
// Stubberというクラスを仲介するので下記みたいな書き方でできる。
// Stubber sb = Mockito.doReturn("mockito!");
// sb.when(h).execute(""); //executeの結果をmockito!に置き換え
// sb.when(h).hogehoge(""); //hogehogeの結果もmockito!に置き換え
// 同じ戻りを使いまわせる。けどいまいちよくわかんない。
・Stubber doAnswer(Answer)
// 使いどころがよくわかんないけど
// thenAnswerと同じ使い方。
・Stubber doNothing()
// Mockito.doNothing().when(h).execute("テスト");
// executeの実行をなかったことにする。
// void戻りのメソッドをなかったことにしたいときに使うんだと思う。
●複数モックの呼び出し順序の検証
・inOrder(Object...)
メソッド呼び出し順序を検証するためのInOrderインスタンスを取得。
InOrder.verify(モックインスタンス).対象メソッド(A);
InOrder.verify(モックインスタンス).対象メソッド(B);
と指定した場合、AのあとにBが呼ばれなければエラーとなる。
●verifyで検証していないメソッド呼び出しが一つもないかを検証する。
・verifyNoMoreInteractions(Object...)
引数にはモックインスタンスを複数指定。
モックしてるのにverifyをしてなかったらエラーになってしまう。
●モックが一度もメソッド呼び出しされないことを検証する。
・verifyZeroInteractions(Object...)
Mockito.never()と似た感じと思われ。
●テストコードのチェック
・validateMockitoUsage()
when()のうしろにthen*がないとか、verify()のうしろにメソッド指定がないとか、
テストとしておかしいっぽい実装がある場合例外が発生する…と思う。