web-dev-qa-db-ja.com

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

いくつかのテストでMockitoを使用しています。

次のクラスがあります。

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

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

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

82
mada

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

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

継承よりも合成を優先

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

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

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

54
iwein

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

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

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
Luke

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
1
dams50

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

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

    void superSave(){
        super.save();
    }
}
0
Rubasace

その理由は、基本クラスがパブリックではないため、基本クラスをパブリックとして変更した場合、またはサブクラスの@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());
}
0
zoufeiyy