web-dev-qa-db-ja.com

mockitoで最終クラスをモックする方法

私は最後のクラスを持っています。

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でこれができますか?

140
buttowski

最終的な/静的なクラス/メソッドのモックはMockito v2でのみ可能です

これをあなたのgradleファイルに追加してください。

testImplementation 'org.mockito:mockito-inline:2.13.0'

これは、Mockito v1では、 Mockito FAQ からは不可能です。

Mockitoの制限は何ですか

  • Java 1.5以降が必要

  • 最終クラスをモックすることはできません

...

91
user180100

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());

その後のマイルストーンで、チームはこの機能を使用するプログラム的な方法をもたらすでしょう。私たちは、疑いのないすべてのシナリオを識別してサポートを提供します。しばらくお待ちください。この機能についてどう思うかお知らせください。

143
WindRider

自分ではできないので、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クラスを作成します。または、あなたがそれを非最終的なものにすることができるならば、それはより良いでしょう。

38

Powermockを使ってください。このリンクは、それを行う方法を示しています: https://github.com/jayway/powermock/wiki/MockFinal

25
Gábor Lipták

これをあなたのgradleファイルに追加してください。

testImplementation 'org.mockito:mockito-inline:2.13.0'

これはmockitoを最終クラスで動作させるための設定です。

21
BennyP

フォローするだけです。この行をあなたのgradleファイルに追加してください。

testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9'

私は様々なバージョンのmockito-coreとmockito-allを試しました。どちらも動作しません。

14
Michael_Zhang

他のクラスがfinalを継承しないようにしたいので、RainOnTreesにしたと思います。 Effective Java が示唆しているように(item 15)、クラスをfinalにせずに拡張のために近い状態に保つ別の方法があります。

  1. finalキーワードを削除してください。

  2. そのコンストラクタをprivateにします。 superコンストラクタを呼び出すことができないため、どのクラスもそれを拡張できません。

  3. クラスをインスタンス化するための静的ファクトリメソッドを作成します。

    // 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を使うことができ、ちょっとした定型コードでクラスを拡張のために閉じたままにすることができます。

11
Flávio Faria

私は同じ問題を抱えていました。私がモックしようとしていたクラスは単純なクラスだったので、私は単純にそのインスタンスを作成してそれを返しました。

6
user1945457

これを試してみてください。

Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));

それは私のために働きました。 「SomeMockableType.class」は、モックまたはスパイにしたいものの親クラスです。someInstanceThatIsNotMockableOrSpyableは、モックまたはスパイにしたい実際のクラスです。

もっと詳しく知りたい方は こちら

6

場合によっては適用できるもう1つの回避策は、その最終クラスによって実装されるインターフェースを作成し、具象クラスではなくそのインターフェースを使用するようにコードを変更してから、そのインターフェースをモックすることです。これにより、コントラクト(インターフェース)を実装(最終クラス)から切り離すことができます。もちろん、あなたが望むことが本当に最後のクラスにバインドすることであるならば、これは当てはまりません。

4
andresp

実際には、スパイに使用する方法が1つあります。 2つの前提条件が満たされている場合にのみ有効です。

  1. あなたはfinalクラスのインスタンスを注入するためにある種のDIを使います
  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();
4
xuesheng

はい、ここでも同じ問題です。Mockitoで最後のクラスをモックすることはできません。正確に言うと、Mockitoは以下をモック/スパイすることはできません。

  • 最終クラス
  • 無名クラス
  • プリミティブ型

しかし、ラッパークラスを使用することは私には大きな代償を払うように思われるので、代わりにPowerMockitoを入手してください。

2
Yu Chen

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));

}

}

それが役に立てば幸い。

完全な記事はここにあります モック - モック不可

2
ksl

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 
2
Ozeetee

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();
        }

    }
}
1
Marcin

RCとLuigi R. Viggianoが一緒に提供する解決策がおそらく最善の策です。

Mockitoはできませんが、設計上、最終クラスを模擬することで、委任アプローチが可能ですこれには利点があります。

  1. それがあなたのAPIがそもそも意図しているものであるなら、あなたはあなたのクラスを非最終クラスに変更することを強制されません(最終クラスには 利点 があります)。
  2. あなたはあなたのAPIの周りに 装飾 の可能性をテストしています。

テストケースでは、テスト中のシステムに意図的に呼び出しを転送します。したがって、設計上、あなたの装飾は何もしませんしません

そのため、テストでは、ユーザーがAPIを拡張するのではなく装飾することしかできないことを実証することもできます。

もっと主観的なメモ:私はフレームワークを最小限に抑えることを好みます。それがJUnitとMockitoが通常私には十分である理由です。実際、このように制限すると、私にも良い意味でリファクタリングを強いられることがあります。

1
Neel

原則的にもっと考える必要があると思います。代わりにあなたのファイナルクラスは代わりに彼のインターフェースとモックインターフェースを使います。

このため:

 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)
        )

    }
1
Serg Burlaka

他の人が述べているように、これはMockitoではそのままではうまくいきません。テスト中のコードで使用されているオブジェクトの特定のフィールドを設定するためにリフレクションを使用することをお勧めします。自分でこれをたくさんやっていると感じたら、この機能をライブラリにラップすることができます。

余談ですが、あなたが最後のクラスをマークするのがあなたのものであるならば、それをするのをやめてください。私はこの質問に出くわしたのですが、私は正当な拡張の必要性(モッキング)を防ぐためにすべてがfinalとマークされたAPIを使用しています。

0
Tom N.

あなたがtestフォルダの下でunit-testを実行しようとしているなら、一番上の解決策は問題ありません。拡張子を追加してください。

しかし、androidtestフォルダの下にあるコンテキストやアクティビティのようなAndroid関連クラスで実行したい場合は、 答え はあなたのためです。

0
Allen Wang

私たちにとって、それは私たちがkoin-testからmockito-inlineを除外したからです。 1つのgradleモジュールが実際にこれを必要としていたため、リリースビルドでは失敗しただけでした(IDEのdebugビルドはうまくいきました):-P

0
kenyee