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


218

私はこのような最終クラスを持っています:

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でこれを行うにはどうすればよいですか?


9
Mockitoでは許可されていませんが、PowerMockでは許可されています。
2013年

1
Mockito 2.x以降、Mockitoは最終的なクラスとメソッドのモックをサポートするようになりました。
ケント

回答:


155

final / staticクラス/メソッドのモックは、Mockito v2でのみ可能です。

これをgradleファイルに追加してください:

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

これはMockito FAQの Mockito v1では不可能です。

Mockitoの制限は何ですか

  • Java 1.5以降が必要

  • 最終クラスを模擬できません

...


2
これはScalaでは機能しませんでした(sbtを変更した場合)。
micseydel

2
これでは十分ではありませんでした。また、baeldung.com / mockito- final
micseydel

204

Mockito 2は最終的なクラスとメソッドをサポートするようになりました!

しかし、今のところ、これは「インキュベーション」機能です。これを有効にするには、Mockito 2の新機能で説明されているいくつかの手順が必要です。

最終的なクラスとメソッドのモックは、インキュベーションのオプトイン機能です。これらのタイプのモック機能を有効にするために、Javaエージェントのインスツルメンテーションとサブクラス化を組み合わせて使用​​します。これは現在のメカニズムとは動作が異なり、これにはさまざまな制限があり、経験とユーザーのフィードバックを収集したいので、この機能を使用可能にするには明示的にアクティブ化する必要がありました。これはsrc/test/resources/mockito-extensions/org.mockito.plugins.MockMaker、1行を含むファイルを作成することにより、mockito拡張メカニズムを介して実行できます。

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

その後のマイルストーンでは、チームはこの機能をプログラムで使用する方法を提供します。私たちはすべてのモックできないシナリオを特定し、サポートを提供します。今後ともよろしくお願いいたします。この機能についてのご意見をお聞かせください。


14
それでもエラーが発生する:クラスandroid.content.ComponentNameをモック/スパイできませんMockitoがモック/スパイできません:-最終クラス
IgorGanapolsky

3
あなたが置くことを確認しorg.mockito.plugins.MockMaker、正しいフォルダ内のファイルを。
WindRider 2017年

7
上記の手順を実行した後でもエラーが発生します。Mockitoは次の理由でモック/スパイできません。-最終クラス
rcde0

8
@vCillusionこの回答は、PowerMockとは関係ありません。
ライン

6
私はこれらの指示に従いましたが、それでもこの作業を行うことができませんでした。誰か他に何かする必要がありましたか?
フランコ

43

Mockitoを使用して最終クラスをモックすることはできません。

私がすることは、最終クラスをラップしてデリゲートとして使用する非最終クラスを作成することです。これの例はTwitterFactoryクラスであり、これは私のモック可能なクラスです:

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など)。

あなたの場合、私は最終RainOnTreesクラスに委任する非最終クラスを作成します。または、それを非ファイナルにできる場合は、それがより良いでしょう。


6
+1。必要に応じて、Lombokのようなものを使用して@Delegate、ボイラープレートの多くを処理できます。
ruakh 2014年

2
@luigiを例として、Junitのコードスニペットを追加できます。最終クラスのラッパーを作成しようとしましたが、それをテストする方法を見つけることができませんでした。
2016

31

これをgradleファイルに追加してください:

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

これは、mockitoを最終クラスで機能させるための構成です


1
おそらく「testCompile」の代わりに「testImplementation」を使用する必要があります。Gradleは「testCompile」をもう好きではありません。
jwehrle

素晴らしいコメント、ありがとう!testImplementationに編集されました。元のコメント:testCompile 'org.mockito:mockito-inline:2.13.0'
BennyP

2
このエラーで結果のLinux / OpenJDKの1.8上で実行されている:org.mockito.exceptions.base.MockitoInitializationException: Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
NAXA

Oracle JDK 1.8に切り替えたときに問題なく機能する
naXa

23

Powermockを使用します。このリンクは、その方法を示しています:https : //github.com/jayway/powermock/wiki/MockFinal


30
PowerMockは、「処方箋」ベースでしか出てこない薬の1つに似ていると思います。つまり、PowerMockには多くの問題があることを明確にする必要があります。それを使用することは、究極の最後の手段のようなものです。そして、できるだけ避けるべきです。
GhostCat 2016年

1
どうしてそんなこと言うの?
PragmaticProgrammer

Powermock最終クラスと静的メソッドのモックに使用して、公式にチェックされたカバレッジを増やしていましたSonarqube。SonarQube以降、カバレッジは0%でした。理由は何であれ、Powermockをその内部で使用するクラスを認識しないためです。私と私のチームは、オンラインのスレッドからそれを実現するのにかなりの時間をかけました。したがって、これはPowermockに注意し、おそらく使用しない理由の1つにすぎません。
AMER

16

ただフォローアップするために。この行をgradleファイルに追加してください:

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

mockito-coreとmockito-allのさまざまなバージョンを試しました。どちらも機能しません。


1
これに加えて、私が観察したことの1つは、Powermockをmockitoと一緒に使用している場合です。次に、「src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker」にモックメーカープラグインファイルを追加しても、最終クラスをモックするのに役立ちません。代わりに、上記のMichael_Zhangで言及されているような依存関係を追加すると、最終クラスのモックの問題が解決されます。また、Mockito1ではなくMockito 2を使用していることを確認してください
dishooom

12

私はあなたがそれを作ったと思いfinalますが、拡張から他のクラスを防ぎたいのでRainOnTrees効果的なJavaが示唆(項目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を使用し、小さなボイラープレートコードを使用してクラスを拡張のために閉じたままにすることができます。


1
これは、mockito 2でもモック可能な最終メソッドでは機能しません。
ルカシュRzeszotarski

11

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


2
絶対に、なぜ単純なクラスをモックするのですか?モッキングは「高価な」相互作用のためです:他のサービス、エンジン、データクラスなど
StripLight

3
そのインスタンスを作成した場合、後でMockito.verifyメソッドを適用することはできません。モックの主な用途は、そのメソッドのいくつかをテストできるようにすることです。
riroo 2016年

6

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

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

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

詳細はこちらをご覧ください


3
デリゲートはネイティブのスパイのモックとは非常に異なることに注意してください。ネイティブのmockitoスパイでは、インスタンス自体の「this」がスパイ自体を参照します(useサブクラスを使用しているため)。ただし、デリゲートでは、「this」が実際のオブジェクトsomeInstanceThatIsNotMockableOrSpyableになります。スパイではありません。したがって、自己呼び出し関数をdoReturn / verifyする方法はありません。
Dennis C

1
例を挙げていただけますか?
Vishwa Ratna

5

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


5

実際、私がスパイに使用する方法が1つあります。次の2つの前提条件が満たされている場合にのみ機能します。

  1. ある種のDIを使用して、最終クラスのインスタンスを注入します
  2. 最終クラスはインターフェースを実装します

効果的なJavaの項目16を思い出してください。ラッパー(最終ではない)を作成し、すべての呼び出しを最終クラスのインスタンスに転送できます。

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

5

Mockito 3以降では、同じ問題があり、このリンクから修正しました

以下の ように、MockitoでFinalクラスとメソッドをモックする

Mockitoを使用して最終的なクラスとメソッドをモックする前に、Mockitoを構成する必要があります。

プロジェクトのsrc / test / resources / mockito-extensionsディレクトリにorg.mockito.plugins.MockMakerという名前のテキストファイルを追加し、1行のテキストを追加する必要があります。

mock-maker-inline

Mockitoは、ロード時に拡張ファイルの設定ファイルをチェックします。このファイルは、最終的なメソッドとクラスのモックを可能にします。


4

Android + Kotlinで同じ問題(Mockito + Final Class)に直面している人のための時間節約。Kotlinと同様に、クラスはデフォルトでfinalです。アーキテクチャコンポーネントを含む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 

これはアプリレベルのbuild.gradleでうまく機能しますが、ライブラリレベルでこれを実現するにはどうすればよいでしょうか。
Sumit T

もう少し詳しく説明してもらえますか?通常、ファサードパターンを使用してlibsに接続します。そして、これらのファサードクラスをモックしてアプリをテストします。この方法では、libクラスをモックする必要はありません。
Ozeetee

3

これは、Mockito2を使用していて、最終的なクラスとメソッドのモックをサポートする新しいインキュベーション機能を使用している場合に実行できます。

注意すべき重要な点:
1.「org.mockito.plugins.MockMaker」という名前のシンプルなファイルを作成し、「mockito-extensions」という名前のフォルダーに配置します。このフォルダーは、クラスパスで使用できるようにする必要があります。
2.上記で作成したファイルの内容は、以下に示すように1行である必要があります:
mock-maker-inline

上記の2つのステップは、mockito拡張メカニズムをアクティブにして、このオプトイン機能を使用するために必要です。

サンプルクラスは次のとおりです。

FinalClass.java

public final class FinalClass {

public final String hello(){
    System.out.println("Final class says Hello!!!");
    return "0";
}

}

Foo.java

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

}

}

それが役に立てば幸い。

ここでは、完全な記事の存在をからかう-unmockableを


ここには回答を含め、外部サイトへのリンクは含めないでください。手順が長い場合は、概要を含めることができます。
rghome 2017

@RunWith(PowerMockRunner.class)@PrepareForTest({AFinalClass.class})をモックするときは、以下のアノテーションが使用されていることを確認してください
vCillusion 2017

1
@vCillusion-私が示した例ではMockito2 APIを使用しています。Mockito2のオプトイン機能を使用すると、Powermockを使用する必要なく、最終クラスを直接モックできます。
ksl 2017

2

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

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

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


2

原則的にもっと考える必要があると思います。代わりに、最終クラスは代わりに彼のインターフェイスとモックインターフェイスを使用します。

このため:

 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

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

RCとLuigi R. Viggianoが一緒に提供するソリューションがおそらく最良のアイデアです。

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

  1. そもそもAPIが意図したとおりにクラスをnon-finalに変更する必要はありません(finalクラスには利点があります)。
  2. あなたはあなたのAPIの周りの装飾の可能性をテストしています。

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

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

より主観的な注意:私はフレームワークを最小限に抑えることを好みます。そのため、通常はJUnitとMockitoで十分です。実際、この方法を制限すると、場合によっては、リファクタリングを強制的に行われることもあります。


1

テストフォルダーの下で単体テストを実行しようとしている場合は、一番上のソリューションが問題ありません。拡張機能を追加するだけです。

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


1

これらの依存関係を追加してmockitoを正常に実行します。

testImplementation 'org.mockito:mockito-core:2.24.5'
testImplementation "org.mockito:mockito-inline:2.24.5"


0

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

余談ですが、もしあなたがクラスを最終的にマークしているのなら、それをやめてください。私が拡張(モック)の正当な必要性を防ぐためにすべてが最終としてマークされたAPIを使用していて、クラスを拡張する必要がないことを開発者が想定していなかったので、この質問に遭遇しました。


1
パブリックAPIクラスは拡張用に開いている必要があります。完全に同意します。ただし、プライベートコードベースでfinalは、デフォルトにする必要があります。
ErikE

0

私たちにとっては、mockito-inlineをkoin-testから除外したからです。1つのGradleモジュールが実際にこれを必要とし、理由はリリースビルドでのみ失敗しました(IDEでのデバッグビルドは機能しました)


0

最後のクラスについては、以下を追加してモックして静的または非静的を呼び出します。

1-これをクラスレベルで追加します@SuppressStatucInitializationFor(value = {パッケージのクラス名})
2- PowerMockito.mockStatic(classname.class)はクラスをモックします
ます。次に、このクラスのメソッドを呼び出すときにwhenステートメントを使用してモックオブジェクトを返します。

楽しい


-5

finalは試しませんでしたが、プライベートの場合は、リフレクションを使用して修飾子を削除しました。さらに確認したところ、最終的には機能しません。


これは尋ねられた質問に答えていません
Sanjit Kumar Mishra '23 / 11/18
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.