今日も懲りずにMockitoネタです。すいません。
近頃、興味深いエラーに出くわしました。
public class Foo { public void doSomething() { final Set<String> strings = ...; this.doOtherthins(strings); } void doOtherthing(final Set<String> strings) { // 何かの処理 } } public class FooTest { @Test public void testDoSomething() { final Foo foo = spy(new Foo()); doNothing().when(foo).doOtherthing(any(Set.class)); // ※ foo.doSomething(); verify(foo).doOtherthing(any(Set.class)); // ※ }
このコードは動作するのですが、※の場所でコンパイラが警告を出します。
なぜなら、Mockito#anyは総称型の値を返せないからです。宣言を見てみると、
/** * any kind object, not necessary of the given class. * The class argument is provided only to avoid casting. * <p> * Sometimes looks better than anyObject() - especially when explicit casting is required * <p> * Alias to {@link Matchers#anyObject()} * <p> * See examples in javadoc for {@link Matchers} class * * @return <code>null</code>. */ public static <T> T any(Class<T> clazz) { return (T) anyObject(); }
というわけなので、anyの戻り値は素のSetになります。
だから、警告を消すために、次のようにしてみました。
public class Foo { public void doSomething() { final Set<String> strings = ...; this.doOtherthins(strings); } void doOtherthing(final Set<String> strings) { // 何かの処理 } } public class FooTest { @Test public void testDoSomething() { final Foo foo = spy(new Foo()); doNothing().when(foo).doOtherthing(any(Set.class)); foo.doSomething(); // 警告除去用 @SuppressWarnings("unchecked") final Set<String> anySet = any(Set.class); verify(foo).doOtherthing(anySet); }
すると、
謎のエラーが発生。
org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Misplaced argument matcher detected here: -> at sample.FooTest.testDoSomething(FooTest.java:10) You cannot use argument matchers outside of verification or stubbing. Examples of correct usage of argument matchers: when(mock.get(anyInt())).thenReturn(null); doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject()); verify(mock).someMethod(contains("foo")) Also, this error might show up because you use argument matchers with methods that cannot be mocked. Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode(). at sample.FooTest(FooTest.java:10) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ...(略)
エラーメッセージによると、どうやらMockito#any系のメソッドはインラインで呼び出す必要があるようです。
つまり、
doOtherthing(any(Set.class));
はOKだけど、
final Set anySet = any(Set.class); doOtherthing(anySet);
はダメだということです。うーむ、何故??
どうやってるのコレ?
『インライン呼び出しかどうか』で制御を分けているなんてビックリです。
これまで、趣味・仕事問わず様々なソースを読んできましたが、どうやってコレを実現しているのか非常に気になるところです。
というわけで、Mockitoの内部で何が起きているのかを調べてみようと思います。
一番ベタな予想だとStackTraceを使って、メソッド呼び出し時の行番号を見ているのではないか?
と一瞬思ったのですが、それだとjavacのコンパイルオプションよって
実行結果が変化してしまう(デバッグ情報抜きでコンパイルした場合)のか。。。じゃ、それはないな。
ま、調査結果はそのうち。。。
ちなみに
上記の例は、フツーに
public <T> Mockito#anySetOf(Class<T> type)
を使えばOK。ご参考まで。