私は最後のクラスを持っています。
public final class RainOnTrees{
public void startRain(){
// some code here
}
}
私はこのような他のクラスでこのクラスを使っています:
public class Seasons{
RainOnTrees rain = new RainOnTrees();
public void findSeasonAndRain(){
rain.startRain();
}
}
そして私のJUnitのSeasons.Java
のテストクラスで、RainOnTrees
クラスをモックしたいのです。どうすればMockitoでこれができますか?
最終的な/静的なクラス/メソッドのモックはMockito v2でのみ可能です
これをあなたのgradleファイルに追加してください。
testImplementation 'org.mockito:mockito-inline:2.13.0'
これは、Mockito v1では、 Mockito FAQ からは不可能です。
Mockitoの制限は何ですか
Java 1.5以降が必要
最終クラスをモックすることはできません
...
Mockito 2は最後のクラスとメソッドをサポートします!
しかし今のところ、それは "インキュベーション"機能です。それはそれをアクティブにするためにいくつかのステップを必要とします--- Mockito 2の新機能 :
最終的なクラスとメソッドのモックは、インキュベーション、オプトイン機能です。これらのタイプのモックアビリティを可能にするために、Javaエージェントのインスツルメンテーションとサブクラス化の組み合わせを使用します。これは現在のメカニズムとは異なる動作をし、これには異なる制限があり、経験とユーザーからのフィードバックを集めたいので、この機能は明示的にアクティブにして利用可能にする必要がありました。これは、mockito拡張メカニズムを介して、1行を含むファイル
src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
を作成することによって実行できます。mock-maker-inline
このファイルを作成した後、Mockitoは自動的にこの新しいエンジンを使用します。
final class FinalClass { final String finalMethod() { return "something"; } } FinalClass concrete = new FinalClass(); FinalClass mock = mock(FinalClass.class); given(mock.finalMethod()).willReturn("not anymore"); assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());
その後のマイルストーンで、チームはこの機能を使用するプログラム的な方法をもたらすでしょう。私たちは、疑いのないすべてのシナリオを識別してサポートを提供します。しばらくお待ちください。この機能についてどう思うかお知らせください。
自分ではできないので、Mockitoで最終クラスをモックすることはできません。
私がしているのは、最終クラスをラップしてデリゲートとして使用するための非最終クラスを作成することです。この例は TwitterFactory
classです、そしてこれは私のモック可能なクラスです:
public class TwitterFactory {
private final Twitter4j.TwitterFactory factory;
public TwitterFactory() {
factory = new Twitter4j.TwitterFactory();
}
public Twitter getInstance(User user) {
return factory.getInstance(accessToken(user));
}
private AccessToken accessToken(User user) {
return new AccessToken(user.getAccessToken(), user.getAccessTokenSecret());
}
public Twitter getInstance() {
return factory.getInstance();
}
}
不利な点は、定型コードがたくさんあることです。利点は、アプリケーションのビジネスに関連する可能性があるメソッドをいくつか追加できることです(上記の場合、accessTokenの代わりにユーザーを取得するgetInstanceなど)。
あなたの場合、私はfinalクラスに委譲するnon-final RainOnTrees
クラスを作成します。または、あなたがそれを非最終的なものにすることができるならば、それはより良いでしょう。
Powermockを使ってください。このリンクは、それを行う方法を示しています: https://github.com/jayway/powermock/wiki/MockFinal
これをあなたのgradleファイルに追加してください。
testImplementation 'org.mockito:mockito-inline:2.13.0'
これはmockitoを最終クラスで動作させるための設定です。
フォローするだけです。この行をあなたのgradleファイルに追加してください。
testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9'
私は様々なバージョンのmockito-coreとmockito-allを試しました。どちらも動作しません。
他のクラスがfinal
を継承しないようにしたいので、RainOnTrees
にしたと思います。 Effective Java が示唆しているように(item 15)、クラスをfinal
にせずに拡張のために近い状態に保つ別の方法があります。
final
キーワードを削除してください。
そのコンストラクタをprivate
にします。 super
コンストラクタを呼び出すことができないため、どのクラスもそれを拡張できません。
クラスをインスタンス化するための静的ファクトリメソッドを作成します。
// No more final keyword here.
public class RainOnTrees {
public static RainOnTrees newInstance() {
return new RainOnTrees();
}
private RainOnTrees() {
// Private constructor.
}
public void startRain() {
// some code here
}
}
この戦略を使うことで、Mockitoを使うことができ、ちょっとした定型コードでクラスを拡張のために閉じたままにすることができます。
私は同じ問題を抱えていました。私がモックしようとしていたクラスは単純なクラスだったので、私は単純にそのインスタンスを作成してそれを返しました。
これを試してみてください。
Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));
それは私のために働きました。 「SomeMockableType.class」は、モックまたはスパイにしたいものの親クラスです。someInstanceThatIsNotMockableOrSpyableは、モックまたはスパイにしたい実際のクラスです。
もっと詳しく知りたい方は こちら
場合によっては適用できるもう1つの回避策は、その最終クラスによって実装されるインターフェースを作成し、具象クラスではなくそのインターフェースを使用するようにコードを変更してから、そのインターフェースをモックすることです。これにより、コントラクト(インターフェース)を実装(最終クラス)から切り離すことができます。もちろん、あなたが望むことが本当に最後のクラスにバインドすることであるならば、これは当てはまりません。
実際には、スパイに使用する方法が1つあります。 2つの前提条件が満たされている場合にのみ有効です。
項目16を Effective Java から思い出してください。ラッパーを作成し(finalではない)、すべての呼び出しをfinalクラスのインスタンスに転送します。
public final class RainOnTrees implement IRainOnTrees {
@Override public void startRain() { // some code here }
}
public class RainOnTreesWrapper implement IRainOnTrees {
private IRainOnTrees delegate;
public RainOnTreesWrapper(IRainOnTrees delegate) {this.delegate = delegate;}
@Override public void startRain() { delegate.startRain(); }
}
今、あなたはあなたの最終クラスをモックするだけでなく、それをスパイすることができるだけではありません:
public class Seasons{
RainOnTrees rain;
public Seasons(IRainOnTrees rain) { this.rain = rain; };
public void findSeasonAndRain(){
rain.startRain();
}
}
IRainOnTrees rain = spy(new RainOnTreesWrapper(new RainOnTrees()) // or mock(IRainOnTrees.class)
doNothing().when(rain).startRain();
new Seasons(rain).findSeasonAndRain();
はい、ここでも同じ問題です。Mockitoで最後のクラスをモックすることはできません。正確に言うと、Mockitoは以下をモック/スパイすることはできません。
しかし、ラッパークラスを使用することは私には大きな代償を払うように思われるので、代わりにPowerMockitoを入手してください。
Mockito2を使用している場合は、最終クラスとメソッドのモックをサポートする新しいインキュベーション機能を使用してこれを行うことができます。
注意すべきポイント:
1。 「org.mockito.plugins.MockMaker」という名前の単純なファイルを作成し、それを「mockito-extensions」という名前のフォルダーに配置します。このフォルダはクラスパスで利用可能にする必要があります。
2。上記で作成したファイルの内容は、次のように1行になります。
モックメーカーインライン
上記の2つの手順は、mockito拡張メカニズムを有効にしてこのオプトイン機能を使用するために必要です。
サンプルクラスは以下の通りです:
FinalClass.Java
public final class FinalClass {
public final String hello(){
System.out.println("Final class says Hello!!!");
return "0";
}
}
フージャバ
public class Foo {
public String executeFinal(FinalClass finalClass){
return finalClass.hello();
}
}
FooTest.Java
public class FooTest {
@Test
public void testFinalClass(){
// Instantiate the class under test.
Foo foo = new Foo();
// Instantiate the external dependency
FinalClass realFinalClass = new FinalClass();
// Create mock object for the final class.
FinalClass mockedFinalClass = mock(FinalClass.class);
// Provide stub for mocked object.
when(mockedFinalClass.hello()).thenReturn("1");
// assert
assertEquals("0", foo.executeFinal(realFinalClass));
assertEquals("1", foo.executeFinal(mockedFinalClass));
}
}
それが役に立てば幸い。
完全な記事はここにあります モック - モック不可 。
Android + Kotlinで同じ問題(Mockito + Final Class)に直面している人のための時間節約。 Kotlinと同様に、クラスはデフォルトでfinalです。 Architectureコンポーネントを使用したGoogle Androidサンプルの1つに解決策が見つかりました。ここから選んだ解決策: https://github.com/googlesamples/Android-architecture-components/blob/master/GithubBrowserSample
以下の注釈を作成します。
/**
* This annotation allows us to open some classes for mocking purposes while they are final in
* release builds.
*/
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass
/**
* Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds.
*/
@OpenClass
@Target(AnnotationTarget.CLASS)
annotation class OpenForTesting
あなたのgradleファイルを修正してください。ここから例を見てください: https://github.com/googlesamples/Android-architecture-components/blob/master/GithubBrowserSample/app/build.gradle
apply plugin: 'kotlin-allopen'
allOpen {
// allows mocking for classes w/o directly opening them for release builds
annotation 'com.Android.example.github.testing.OpenClass'
}
これで、テスト用に公開するために任意のクラスに注釈を付けることができます。
@OpenForTesting
class RepoRepository
JMockit をご覧ください。それは多くの例を含む広範なドキュメントを持っています。ここにあなたの問題の解決策の例があります(簡単にするために、モックアップされたSeasons
インスタンスを注入するためにRainOnTrees
にコンストラクタを追加しました)。
package jmockitexample;
import mockit.Mocked;
import mockit.Verifications;
import mockit.integration.junit4.JMockit;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(JMockit.class)
public class SeasonsTest {
@Test
public void shouldStartRain(@Mocked final RainOnTrees rain) {
Seasons seasons = new Seasons(rain);
seasons.findSeasonAndRain();
new Verifications() {{
rain.startRain();
}};
}
public final class RainOnTrees {
public void startRain() {
// some code here
}
}
public class Seasons {
private final RainOnTrees rain;
public Seasons(RainOnTrees rain) {
this.rain = rain;
}
public void findSeasonAndRain() {
rain.startRain();
}
}
}
RCとLuigi R. Viggianoが一緒に提供する解決策がおそらく最善の策です。
Mockitoはできませんが、設計上、最終クラスを模擬することで、委任アプローチが可能ですこれには利点があります。
テストケースでは、テスト中のシステムに意図的に呼び出しを転送します。したがって、設計上、あなたの装飾は何もしませんしません。
そのため、テストでは、ユーザーがAPIを拡張するのではなく装飾することしかできないことを実証することもできます。
もっと主観的なメモ:私はフレームワークを最小限に抑えることを好みます。それがJUnitとMockitoが通常私には十分である理由です。実際、このように制限すると、私にも良い意味でリファクタリングを強いられることがあります。
原則的にもっと考える必要があると思います。代わりにあなたのファイナルクラスは代わりに彼のインターフェースとモックインターフェースを使います。
このため:
public class RainOnTrees{
fun startRain():Observable<Boolean>{
// some code here
}
}
追加する
interface iRainOnTrees{
public void startRain():Observable<Boolean>
}
そしてあなたのインターフェースをモックする:
@Before
fun setUp() {
rainService= Mockito.mock(iRainOnTrees::class.Java)
`when`(rainService.startRain()).thenReturn(
just(true).delay(3, TimeUnit.SECONDS)
)
}
他の人が述べているように、これはMockitoではそのままではうまくいきません。テスト中のコードで使用されているオブジェクトの特定のフィールドを設定するためにリフレクションを使用することをお勧めします。自分でこれをたくさんやっていると感じたら、この機能をライブラリにラップすることができます。
余談ですが、あなたが最後のクラスをマークするのがあなたのものであるならば、それをするのをやめてください。私はこの質問に出くわしたのですが、私は正当な拡張の必要性(モッキング)を防ぐためにすべてがfinalとマークされたAPIを使用しています。
あなたがtestフォルダの下でunit-testを実行しようとしているなら、一番上の解決策は問題ありません。拡張子を追加してください。
しかし、androidtestフォルダの下にあるコンテキストやアクティビティのようなAndroid関連クラスで実行したい場合は、 答え はあなたのためです。
私たちにとって、それは私たちがkoin-testからmockito-inlineを除外したからです。 1つのgradleモジュールが実際にこれを必要としていたため、リリースビルドでは失敗しただけでした(IDEのdebugビルドはうまくいきました):-P