2013年6月21日金曜日

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

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

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

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

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

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



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

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




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

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

あとはこの ArgumentCaptor の capture() を引数として渡します。
  1. /** 
  2.  * Status キーがある場合 handleJson() が呼ばれ、引数が Status キーの値であることを確認する 
  3.  */  
  4. public void testStatusKey() {  
  5.     Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener);  
  6.   
  7.     ArgumentCaptor<String> statusCaptor = ArgumentCaptor  
  8.             .forClass(String.class);  
  9.     ArgumentCaptor<Long> timeCaptor = ArgumentCaptor.forClass(Long.class);  
  10.   
  11.     verify(mockResultListener, only()).handleStatus(statusCaptor.capture(),  
  12.             timeCaptor.capture());  
  13.   
  14.     String status = statusCaptor.getValue();  
  15.     long time = timeCaptor.getValue();  
  16.     assertEquals("hoge1", status);  
  17.   
  18.     System.out.println("Status = " + status + ", time = " + time);  
  19. }  
verify() 後は ArgumentCaptor の getValue() で値を取ることが出来ます。


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

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





1 件のコメント:

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

    mockit、とても便利ですね!

    返信削除