メッセージは非常に明白です。非表示の最終クラスをモックすることはできません。簡単な答え:匿名のクラスの名前付きクラスを作成し、代わりにこのクラスをテストしてください!
長い答え、理由を掘り下げましょう!
匿名のクラスは最終です
の匿名クラスをインスタンス化しますFilterFactory
。コンパイラが匿名クラスを検出すると、最終的なパッケージ表示クラスを作成します。したがって、匿名クラスは、標準的な手段、つまりMockitoを介してモックすることはできません。
匿名クラスのモック:可能ですが、HACKYでない場合はBRITTLE
では、Powermockを介してこの匿名クラスをモックできるようにしたいとします。現在のコンパイラは、次のスキームで匿名クラスをコンパイルします。
Declaring class + $ + <order of declaration starting with 1>
匿名クラスをあざけることは可能ですが、もろいです(つまり、それを意味します)したがって、匿名クラスが宣言される11番目であるとすると、次のように表示されます。
InputHelper$11.class
したがって、匿名クラスのテストの準備をする可能性があります。
@RunWith(PowerMockRunner.class)
@PrepareForTest({InputHelper$11.class})
public class InputHelperTest {
@Test
public void anonymous_class_mocking works() throws Throwable {
PowerMockito.spy(InputHelper.BZIP2_FACTORY); // This line fails
}
}
このコードはコンパイルされますが、最終的にはIDEのエラーとして報告されます。IDEはおそらくについて知りませんInputHelper$11.class
。コードレポートをチェックするためにコンパイルされたクラスを使用しないIntelliJ。
また、匿名クラスの命名が実際には宣言の順序に依存するという事実も問題です。誰かが以前に別の匿名クラスを追加すると、番号が変更される可能性があります。匿名クラスは匿名のままになります。コンパイラーがいつか文字やランダムな識別子を使用することにした場合はどうでしょうか。
したがって、Powermockを介して匿名クラスをモックすることは可能ですが、脆弱です。実際のプロジェクトでは絶対に行わないでください。
編集済み注: Eclipseコンパイラには異なる番号付けスキームがあり、常に3桁の番号を使用します:
Declaring class + $ + <pad with 0> + <order of declaration starting with 1>
また、JLSは、コンパイラが匿名クラスに名前を付ける方法を明確に指定していないと思います。
スパイを静的フィールドに再割り当てしません
PowerMockito.spy(InputHelper.BZIP2_FACTORY); // This line fails
whenNew(BufferedInputStream.class).withArguments(in).thenReturn(buffer);
whenNew(CBZip2InputStream.class).withArguments(buffer).thenReturn(expected);
InputStream observed = InputHelper.BZIP2_FACTORY.makeFilter(in);
PowerMockito.spy
スパイを返しますが、の値は変更されませんInputHelper.BZIP2_FACTORY
。したがって、このフィールドをリフレクションを介して実際に設定する必要があります。Whitebox
Powermockが提供するユーティリティを使用できます。
結論
匿名フィルターがを使用するモックでテストするにはあまりにも多くの問題がありBufferedInputStream
ます。
別
私はむしろ次のコードを書きたいです:
名前付きクラスを使用する入力ヘルパーです。このフィルターの目的をユーザーに明確にするために、インターフェイス名は使用しません。
public class InputHelper {
public static final BufferedBZIP2FilterFactory BZIP2_FACTORY = new BufferedBZIP2FilterFactory();
}
そして今、フィルター自体:
public class BufferedBZIP2FilterFactory {
public InputStream makeFilter(InputStream in) {
BufferedInputStream buffer = new BufferedInputStream(in);
return new CBZip2InputStream(buffer);
}
}
今、あなたはこのようなテストを書くことができます:
@RunWith(PowerMockRunner.class)
public class BufferedBZIP2FilterFactoryTest {
@Test
@PrepareForTest({BufferedBZIP2FilterFactory.class})
public void wraps_InputStream_in_BufferedInputStream() throws Exception {
whenNew(CBZip2InputStream.class).withArguments(isA(BufferedInputStream.class))
.thenReturn(Mockito.mock(CBZip2InputStream.class));
new BufferedBZIP2FilterFactory().makeFilter(anInputStream());
verifyNew(CBZip2InputStream.class).withArguments(isA(BufferedInputStream.class));
}
private ByteArrayInputStream anInputStream() {
return new ByteArrayInputStream(new byte[10]);
}
}
CBZip2InputStream
ただし、を強制的に受け入れる場合は、最終的にこのテストシナリオのpowermockを回避できますBufferedInputStream
。通常、Powermockを使用するということは、設計に問題があることを意味します。私の意見では、Powermockはレガシーソフトウェアには最適ですが、新しいコードを設計するときに開発者を盲目にする可能性があります。彼らはOOPの良い部分のポイントを欠いているので、私は彼らがレガシーコードを設計しているとさえ言うでしょう。
お役に立てば幸いです。