2013年6月21日金曜日

Android で mockito を使う : メソッド呼び出し時の引数をチェックする

JSON をパースして、リスナーの対応するメソッドを呼び出すユーティリティメソッドがあるとします。
"Status" というキーの値(文字列)とそのときの時間(long)を handleStatus() の引数として渡すようになっています。 public class Utils { public interface ResultListener { void onError(); void handleStatus(String status, long time); } public static void handleJson(String json, ResultListener listener) { if (listener == null) { return; } try { JSONObject obj = new JSONObject(json); String status = obj.optString("Status"); listener.handleStatus(status, System.currentTimeMillis()); } catch (JSONException e) { listener.onError(); e.printStackTrace(); } } } まずメソッドが呼ばれるかどうかをテストしましょう。
前回紹介したように Mockito.verify() メソッドを使います。 /** * Status キーがある場合、handleJson() が呼ばれることを確認する */ public void testStatusKey() { Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener); verify(mockResultListener, only()).handleStatus(anyString(), anyLong()); } handleStatus() は引数として String と long をとるので verify(mockResultListener, only()).handleStatus(); のように指定することはできません。
そこで、「引数の文字列がどんな値であっても気にしない、メソッドが呼び出されているかどうかだけ確かめたい」という場合、anyString() や anyLong() を指定することができます。
String, long 以外にも anyInt(), anyBoolean() などが用意されています。

引数の値をチェックするために、引数に直接文字列を指定することができます。

Status キーの値は正しい値を指定することができますが、long の引数は handleStatus() 内で System.currentTimeMillis() しているため、次のテストは失敗します。

失敗するテスト /** * Status キーがある場合 handleJson() が呼ばれ、引数が Status キーの値であることを確認する */ public void testStatusKey() { Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener); verify(mockResultListener, only()).handleStatus("hoge1", System.currentTimeMillis()); } そこで long は値を検証しないことにして、anyLong() を使って次のように書くとまたまたテストに失敗します。

失敗するテスト /** * Status キーがある場合 handleJson() が呼ばれ、引数が Status キーの値であることを確認する */ public void testStatusKey() { Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener); verify(mockResultListener, only()).handleStatus("hoge1", anyLong()); } 実は any〇〇() と実際の値を並記することはできません。any〇〇() を使う場合、実際の値の部分には eq() を使います。 /** * Status キーがある場合 handleJson() が呼ばれ、引数が Status キーの値であることを確認する */ public void testStatusKey() { Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener); verify(mockResultListener, only()).handleStatus(eq("hoge1"), anyLong()); }


Status キーの値は optString() で取得しているため、Status キーが無い場合は空文字になります。

失敗するテスト /** * Status キーが無い場合、handleJson() が呼ばれ引数が空文字であることを確認する */ public void testNoStatusKey() { Utils.handleJson("{\"State\":\"hoge1\"}", mockResultListener); verify(mockResultListener, only()).handleStatus(eq(""), anyLong()); }



引数をログに出力したいということがあるでしょう。そのためには verify 時に引数をキャッチしておく必要があります。
これを行ってくれるのが ArgumentCaptor です。

ここでは String と long の引数をキャッチしたいので ArgumentCaptor<String> statusCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor<Long> timeCaptor = ArgumentCaptor.forClass(Long.class); のようにして ArgumentCaptor のインスタンスを作ります。

あとはこの ArgumentCaptor の capture() を引数として渡します。 /** * Status キーがある場合 handleJson() が呼ばれ、引数が Status キーの値であることを確認する */ public void testStatusKey() { Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener); ArgumentCaptor<String> statusCaptor = ArgumentCaptor .forClass(String.class); ArgumentCaptor<Long> timeCaptor = ArgumentCaptor.forClass(Long.class); verify(mockResultListener, only()).handleStatus(statusCaptor.capture(), timeCaptor.capture()); String status = statusCaptor.getValue(); long time = timeCaptor.getValue(); assertEquals("hoge1", status); System.out.println("Status = " + status + ", time = " + time); } verify() 後は ArgumentCaptor の getValue() で値を取ることが出来ます。


ArgumentCaptor.capture() と any〇〇() は並記することができます verify(mockResultListener, only()).handleStatus(anyString(), timeCaptor.capture()); 一方、直接引数を渡す場合とは並記できません。

失敗するテスト verify(mockResultListener, only()).handleStatus("hoge1", timeCaptor.capture()); eq() を使えば大丈夫です。 verify(mockResultListener, only()).handleStatus(eq("hoge1"), timeCaptor.capture());




1 件のコメント:

  1. おはようございます。

    mockit、とても便利ですね!

    返信削除