Mockito~doReturn/whenとwhen/thenReturnの違い

2018年6月29日

Mockitoのスタブ化には二通りの書き方がある。以下のようなものだ。

import org.junit.*;
import org.mockito.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

public class Sample {

  @Mock Login login;

  @Before
  public void before() {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void test() {
    // こちらか
    doReturn(true).when(login).doit(anyString(), anyString());

    // あるいはこちら
    when(login.doit(anyString(), anyString())).thenReturn(true);

    assertTrue(login.doit("a", "b"));
  }

  public interface Login {
    public boolean doit(String name, String password);
  }
}

この違いは何だろうか?

StackOverflowでの答え1

Mockito – difference between doReturn() and when()に解説してくれている人がいる。

二つの構文はおおよそ同じだ。しかし、doReturn/whenはいつでも使えるのに対し、when/thenReturnは使えないケースがある。voidメソッドをスタブ化するのがその一つだ。他の例としては、Mockitoのspyを使う場合や、一つのメソッドを複数回スタブ化する場合だ。
doReturn/whenには無い、when/thenReturnがもたらしてくれるものとしては、コンパイル時の返り値タイプチェックだ。しかし、これにはほぼ価値が無いと思う。もし型が間違っていれば、テストを走らせるやいなやわかることだからね。
強くおすすめするのは、doReturn/whenのみを使うことだ。片方でできるのに、二つの構文を学ぶ理由は無いよね。
僕の書いたForming Mockito “grammers”を読んでみて欲しい。今回の質問に非常に近い、詳細な答えを書いている。

StackOverflowでの答え2

上述のForming Mockito “grammers”における回答者の答え。

when/thenReturn、when/thenThrow、when/then構文には様々不利な点がある。例えば、

  • when/thenReturnでは、返り値がワイルドカード付ジェネリックの場合で、同じ型を返したい場合には、コンパイラの警告が不可避だ。
  • voidメソッドにはwhen/thenThrowが使用できない。
  • Mockitoのspyにはこれらの構文は使用できない。
  • モックオブジェクト、メソッド、引数のそれぞれの組み合わせに対して一度しかwhenを呼び出せない。モックに対してreset()を行わない限りは。
  • 引数マッチャーを使用中に、一つのモックオブジェクト、メソッド、引数の組み合わせに対して、複数回whenを呼び出すと、問題を起こしかねない。

これらのケースを覚えておくのは難しいと感じた。だから、when/thenReturn, when/thenThrow, when/thenがいかなる場合に機能して機能しないかを追うよりも、おすすめは、これらは完全に排除することだ。代替の doReturn/when, doThrow/when, doAnswer/whenを使うってこと。

つまり、doReturn/when, doThrow/when,doAnswer/whenを使う場合もあるということではなく、いつもこれらのメソッドを使うということだ。when/thenReturn, when/thenThrow, when/thenを学習する必要は無い。

注意して欲しいのは、doReturn, doThrow, doAnswerが、 thenReturn, thenThrow, thenと同じく互いにチェイン可能だということ、これらに存在しないのは、一度のdoReturn, doThrow, doAnswerの呼び出しで複数の値を返すことだ(あるいは、複数の例外を返したり、複数のAnswerを行うこと)。でも、これが必要なのは稀だと思うから、重大なことではない。

※訳注:2.1.0から、doReturn,doThrowは複数の値を返せるようになっている。

もう一つ、doReturnの取るに足らない弱点がある。コンパイル時の引数タイプチェックができないことだ、when/thenReturnのようには。だから、引数タイプが間違っている場合は、テストの実行まではわからない。率直に言って、僕は気にしないね。

要約すれば、僕は二年以上Mockitoを使ってきており、常に doReturn, doThrow, doAnswer を使うのがMockitoのベスト・プラクティスだと思ってる。他のユーザは不賛成だけどね。