Mockitoスーパークラスのメソッドの呼び出しのみをモックする方法


94

一部のテストでMockitoを使用しています。

次のクラスがあります。

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        //some code  
        super.save();
    }  
}   

の2番目の呼び出し(super.save)のみをモックしたいChildService。最初の呼び出しは、実際のメソッドを呼び出す必要があります。それを行う方法はありますか?


これはPowerMockitoで解決できますか?
javaPlease42 2016年

@ javaPlease42:はい、できます:stackoverflow.com/a/23884011/2049986
Jacob van Lingen、

回答:


57

いいえ、Mockitoはこれをサポートしていません。

これはあなたが探している答えではないかもしれませんが、あなたが見ているのは、設計原理を適用しないことの症状です:

継承よりも構成を優先する

スーパークラスを拡張する代わりに戦略を抽出すると、問題はなくなります。

ただし、コードを変更することは許可されていませんが、とにかくコードをテストする必要がある場合、この厄介な方法で、まだ希望があります。一部のAOPツール(AspectJなど)を使用すると、コードをスーパークラスメソッドに組み込み、その実行を完全に回避できます(残念ながら)。プロキシを使用している場合、これは機能しません。バイトコード変更(ロード時ウィービングまたはコンパイル時ウィービング)を使用する必要があります。PowerMockやPowerMockitoなど、この種のトリックをサポートするモックフレームワークもあります。

私はリファクタリングに行くことをお勧めしますが、それが選択肢でない場合は、いくつかの深刻なハッキングを楽しむことができます。


5
LSP違反はありません。OPとほぼ同じ設定です。findAll()メソッドを含む基本DAOクラスと、super.findAll()を呼び出して結果をソートすることで基本メソッドをオーバーライドするサブクラスDAOです。サブクラスは、スーパークラスを受け入れるすべてのコンテキストに代入できます。あなたの意味を誤解していますか?

1
LSPのコメントを削除します(答えに価値はありません)。
iwein、2011

はい、継承はうまくいきません。私が行き詰まっている愚かなフレームワークは、継承を唯一のオプションとして設計されています。
Sridhar Sarnobat

スーパークラスを再設計できないと仮定すると、//some codesコードを個別にテストできるメソッドに抽出できます。
Phasmal

1
はい、わかった。これは私がこれを探したときに解決しようとしていた問題とは別の問題ですが、少なくともその誤解によって、基本クラスからのモック呼び出しに対する実際の問題が解決されました(実際にはオーバーライドしません)。
Guillaume Perrot 2017

86

リファクタリングの選択肢がない場合は、スーパーメソッド呼び出しですべてをモック/スタブできます。

    class BaseService {

        public void validate(){
            fail(" I must not be called");
        }

        public void save(){
            //Save method of super will still be called.
            validate();
        }
    }

    class ChildService extends BaseService{

        public void load(){}

        public void save(){
            super.save();
            load();
        }
    }

    @Test
    public void testSave() {
        ChildService classToTest = Mockito.spy(new ChildService());

        // Prevent/stub logic in super.save()
        Mockito.doNothing().when((BaseService)classToTest).validate();

        // When
        classToTest.save();

        // Then
        verify(classToTest).load();
    }

2
このコードは実際にはsuper.save()の呼び出し権を妨げないため、super.save()で多くのことを行う場合、それらのすべての呼び出しを防止する必要があります...
iwein

子供が使用するためにスーパークラスメソッドからモック値を返したいとき、素晴らしい解決策は私にとって不思議に機能します。素晴らしいありがとうございます。
ガーナード

14
これは、validateが非表示になっていないか、またはsaveメソッドが他のメソッドを呼び出す代わりに直接機能しない限り、うまく機能します。mockitoは行いません:Mockito.doNothing()。when((BaseService)spy).save(); これは、基本サービスの保存では「doNothing」ではなく、childService save :(
tibi

私はこれを動作させることができません-それは子メソッドもスタブします。BaseServiceそれが関連する理由はわかりませんが、抽象的です。
Sridhar Sarnobat

1
@ Sridhar-Sarnobatええ、私は同じことを見ています:(誰もがそれをスタブにするだけの方法を知っていsuper.validate()ますか?
スタントン

4

ChildService.save()メソッドから別のメソッドにコードをリファクタリングし、ChildService.save()をテストする代わりに新しいメソッドをテストすることを検討してください。これにより、スーパーメソッドへの不要な呼び出しを回避できます。

例:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 

1

スーパークラスメソッドを呼び出すサブクラスでパッケージ保護(同じパッケージのテストクラスを想定)メソッドを作成し、オーバーライドされたサブクラスメソッドでそのメソッドを呼び出します。次に、スパイパターンを使用して、テストでこのメソッドに期待値を設定できます。かなりではありませんが、テストでスーパーメソッドのすべての期待設定を処理する必要があるよりは確かに優れています。


継承よりも構成はほとんど常に優れていると言えますが、継承を使用するほうが単純な場合もあります。javaがscalaやgroovyなどのより良い構成モデルを組み込むまでは、これが常に当てはまり、この問題は引き続き存在します
Luke

1

iweinの応答に完全に同意したとしても(

継承よりも構成を優先する

)、ときどき継承が自然に見える場合があることを認め、ユニットテストのためだけにそれを壊したりリファクタリングしたりしません。

だから、私の提案:

/**
 * BaseService is now an asbtract class encapsulating 
 * some common logic callable by child implementations
 */
abstract class BaseService {  
    protected void commonSave() {
        // Put your common work here
    }

    abstract void save();
}

public ChildService extends BaseService {  
    public void save() {
        // Put your child specific work here
        // ...

        this.commonSave();
    }  
}

そして、単体テストでは:

    ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS);

    Mockito.doAnswer(new Answer<Void>() {
        @Override
        public Boolean answer(InvocationOnMock invocation)
                throws Throwable {
            // Put your mocked behavior of BaseService.commonSave() here
            return null;
        }
    }).when(childSrv).commonSave();

    childSrv.save();

    Mockito.verify(childSrv, Mockito.times(1)).commonSave();

    // Put any other assertions to check child specific work is done

0

その理由は、基本クラスが公開されていないため、可視性のためにMockitoがそれをインターセプトできないためです。基本クラスをパブリックとして変更するか、サブクラスの@Override(パブリックとして)を変更すると、Mockitoはそれを正しくモックできます。

public class BaseService{
  public boolean foo(){
    return true;
  }
}

public ChildService extends BaseService{
}

@Test
@Mock ChildService childService;
public void testSave() {
  Mockito.when(childService.foo()).thenReturn(false);

  // When
  assertFalse(childService.foo());
}

8
これはポイントではありません。ChildServiceは(FOOをオーバーライドする必要があります)、問題は(BaseService.fooを模擬する方法である)ではなくChildService.foo()
アドリアンコスター

0

継承が理にかなっている場合、おそらく最も簡単なオプションは、スーパーを呼び出す新しいメソッド(パッケージprivate ??)を作成し(それをsuperFindallと呼びましょう)、実際のインスタンスをスパイしてから、モックしたい方法でsuperFindAll()メソッドをモックすることです。親クラス1。カバレッジと可視性の点で完璧なソリューションではありませんが、それで十分であり、簡単に適用できます。

 public Childservice extends BaseService {
    public void save(){
        //some code
        superSave();
    }

    void superSave(){
        super.save();
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.