TL;DR
-
Mockito.mock(Class, Answer)
でデフォルトの返り値を設定しよう。
Fluent Interface についてざっくり復習
最近は Fluent なインタフェースを取り入れたものが多くなってきています。
例えば、以下のようなインタフェースは Fluent なインタフェースと呼ばれます。
public interface SomethingFluent {
SomethingFluent foo();
SomethingFluent bar();
SomethingFluent baz();
}
これを使うコードを見てみると、メソッドの呼び出しを数珠つなぎにすることで、オブジェクトにどのような状態の変化が起こるかがひと目で理解できるようになっていることがわかると思います。
SomethingFluent o1 = new ConcreteSomethingFluent().foo().bar(); // foo と bar を呼んだ状態の SomethingFluent
SomethingFluent o2 = new ConcreteSomethingFluent().baz().bar(); // baz と bar を呼んだ状態の SomethingFluent
SomethingFluent o3 = new ConcreteSomethingFluent().foo().bar().baz(); // 全てを呼んだ状態の SomethingFluent
Fluent Interface をモックする
ここで、このSomethingFluent
に依存したクラスTestTarget
のテストを考えます。
public class TestTarget {
SomethingFluent fluent;
public TestTarget(SomethingFluent fluent) {
this.fluent = fluent;
}
public method1() {
fluent.foo();
}
public method2() {
fluent.foo().bar();
}
}
コンストラクタ経由でSomethingFluent
の依存を注入できるので、ここでSomethingFluent
のモックを流し込みます。
Mockito を使えば簡単ですね。もし Mockito を使わないのであれば、自分でモック実装を作ることになります。
public class TestTargetTest {
private SomethingFluent dependency;
private TestTarget target;
@Before
public void setUp() {
dependency = mock(SomethingFluent.class);
target = new TestTarget(dependency);
}
@Test
public void test_method1() {
target.method1();
verify(dependency).foo();
verify(dependency, never()).bar();
}
@Test
public void test_method2() {
target.method2(); // causing NullPointerException
verify(dependency).foo();
verify(dependency).bar();
}
}
Mockito.mock(Class)
を使う場合、モックオブジェクトが持つ各メソッドの返り値は、void
でない限りnull
か各型のデフォルト値になります。つまり、SomethingFluent
の場合は、全てのメソッドがSomethingFluent
型のオブジェクトを返しますので、モックしたメソッドを呼び出すと常にnull
が返ってくることになります。
すると、TestTarget#method1()
は困ったことは起こりませんが、TestTarget#method2()
では困ったことが起こります。
なぜなら、メソッド呼び出しを数珠つなぎにしてしまっているので、SomethingFluent#foo()
を呼んだ返り値がnull
となり、bar()
の呼び出しでNullPointerException
となるからです。
デフォルトの返り値を設定する
愚直には、Mockito で各メソッドの返り値をそれぞれごとに設定できるようになっているため、それを全てのメソッドに対して行うことになりますが、いかんせんそれは非常に面倒です。そして大抵そのことが忘れ去られ、新しいメソッドを追加したときに無駄にテストがこけてしまいます。
そこで登場するのが、Mockito.mock(Class, Answer)
です。
第二引数のAnswer
は、各モックメソッドがデフォルトで返す返り値を設定するものです。このAnswer
を適切に設定することで、null
以外の、任意のオブジェクトが個別に設定しなくても返ってくるようになります。
今回は、Fluent なインタフェースですから、自分自身を返す必要があります。そこで、以下のようにモックメソッドの呼び出しによってモックオブジェクトを返すようにします。
public class TestTargetTest {
private SomethingFluent dependency;
private TestTarget target;
@Before
public void setUp() {
dependency = mock(SomethingFluent.class, (Answer) invocation -> dependency);
target = new TestTarget(dependency);
}
@Test
public void test_method1() {
target.method1();
verify(dependency).foo();
verify(dependency, never()).bar();
}
@Test
public void test_method2() {
target.method2(); // no problem!
verify(dependency).foo();
verify(dependency).bar();
}
}
これで、Fluent なインタフェースの返り値が無事設定できました。