Mockitoを使用して抽象クラスをテストする


213

抽象クラスをテストしたいのですが。確かに、クラスから継承したモック手動で作成できます。

モックを手作りする代わりに、モックフレームワーク(Mockitoを使用しています)を使用してこれを実行できますか?どうやって?


2
Mockitoのとおり1.10.12、Mockitoの支持体は、直接抽象クラスをからかっ/スパイ:SomeAbstract spy = spy(SomeAbstract.class);
pesche

6
Mockito 2.7.14以降では、コンストラクタ引数を必要とする抽象クラスをモックすることもできますmock(MyAbstractClass.class, withSettings().useConstructor(arg1, arg2).defaultAnswer(CALLS_REAL_METHODS))
Gediminas

回答:


315

次の提案では、「実際の」サブクラスを作成せずに抽象クラスをテストしましょう-モックサブクラスです。

を使用してからMockito.mock(My.class, Mockito.CALLS_REAL_METHODS)、呼び出されるすべての抽象メソッドをモックします。

例:

public abstract class My {
  public Result methodUnderTest() { ... }
  protected abstract void methodIDontCareAbout();
}

public class MyTest {
    @Test
    public void shouldFailOnNullIdentifiers() {
        My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
        Assert.assertSomething(my.methodUnderTest());
    }
}

注:このソリューションの美しさは、あなたがいないということである持っている限り、彼らが呼び出されることはありませんよう、抽象メソッドを実装します。

私の正直な意見では、スパイはインスタンスを必要とするため、これはスパイを使用するよりも優れています。つまり、抽象クラスのインスタンス化可能なサブクラスを作成する必要があります。


14
後述するように、これは、抽象クラスがテストのために抽象メソッドを呼び出す場合には機能しません。これはよくあるケースです。
Richard Nichols、

11
これは、抽象クラスが抽象メソッドを呼び出すときに実際に機能します。Mockito.whenの代わりにdoReturnまたはdoNothing構文を使用して、抽象メソッドをスタブします。具体的な呼び出しをスタブする場合は、必ず抽象呼び出しを先にスタブしてください。
Gonen I

2
この種類のオブジェクト(実際のメソッドを呼び出す模擬抽象クラス)に依存関係を注入するにはどうすればよいですか?
サミュエル

2
問題のクラスにインスタンス初期化子がある場合、これは予期しない方法で動作します。Mockitoはモックの初期化子をスキップします。つまり、インラインで初期化されるインスタンス変数が予期せずnullになり、NPEが発生する可能性があります。
digitalbath 2017

1
抽象クラスのコンストラクターが1つ以上のパラメーターを取る場合はどうなりますか?
SD

68

アブストラクトに触れずにいくつかの具象メソッドをテストする必要がある場合は使用できますCALLS_REAL_METHODSMortenの回答を参照)。ただし、テスト中の具象メソッドがいくつかのアブストラクトまたは実装されていないインターフェースメソッドを呼び出す場合、これは機能しません。 -Mockitoは「Javaインターフェースで実際のメソッドを呼び出せません」と文句を言います。

(はい、それはお粗末なデザインですが、タペストリー4などのいくつかのフレームワークはあなたに強い力を与えます。)

回避策は、このアプローチを逆にすることです。通常のモック動作(つまり、すべてがモック/スタブされている)を使用doCallRealMethod()し、テスト対象の具象メソッドを明示的に呼び出すために使用します。例えば

public abstract class MyClass {
    @SomeDependencyInjectionOrSomething
    public abstract MyDependency getDependency();

    public void myMethod() {
        MyDependency dep = getDependency();
        dep.doSomething();
    }
}

public class MyClassTest {
    @Test
    public void myMethodDoesSomethingWithDependency() {
        MyDependency theDependency = mock(MyDependency.class);

        MyClass myInstance = mock(MyClass.class);

        // can't do this with CALLS_REAL_METHODS
        when(myInstance.getDependency()).thenReturn(theDependency);

        doCallRealMethod().when(myInstance).myMethod();
        myInstance.myMethod();

        verify(theDependency, times(1)).doSomething();
    }
}

追加のために更新:

void以外のメソッドについては、thenCallRealMethod()代わりに使用する必要があります。例:

when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();

それ以外の場合、Mockitoは「未完了のスタブが検出されました」と文句を言います。


9
これは一部のケースで機能しますが、Mockitoはこのメソッドで基本となる抽象クラスのコンストラクターを呼び出しません。これにより、予期しないシナリオが作成され、「実際の方法」が失敗する可能性があります。したがって、この方法はすべてのケースで機能するわけではありません。
Richard Nichols、

3
ええ、オブジェクトの状態はまったく当てにできません。呼び出されているメソッドのコードだけです。
David Moles

ああ、それでオブジェクトメソッドは状態から分離されます、素晴らしい。
haelix 2018年

17

これを行うには、スパイを使用します(ただし、最新バージョンのMockito 1.8以降を使用してください)。

public abstract class MyAbstract {
  public String concrete() {
    return abstractMethod();
  }
  public abstract String abstractMethod();
}

public class MyAbstractImpl extends MyAbstract {
  public String abstractMethod() {
    return null;
  }
}

// your test code below

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));

14

モックフレームワークは、テストするクラスの依存関係を簡単にモックアウトできるように設計されています。モックフレームワークを使用してクラスをモックすると、ほとんどのフレームワークはサブクラスを動的に作成し、メソッドの実装を、メソッドが呼び出されたときに検出して偽の値を返すコードに置き換えます。

抽象クラスをテストするときは、テスト対象(SUT)の非抽象メソッドを実行する必要があるため、モックフレームワークは必要ありません。

混乱の一部は、あなたがリンクした質問への答えが、あなたの抽象クラスから伸びるモックを手作りすることだと言ったことです。私はそのようなクラスをモックとは呼びません。モックは、依存関係の代わりとして使用されるクラスであり、期待値でプログラムされ、これらの期待値が満たされているかどうかを照会することができます。

代わりに、テストで抽象クラスの非抽象サブクラスを定義することをお勧めします。その結果、コードが多すぎる場合は、クラスの拡張が難しいことを示している可能性があります。

代替ソリューションは、SUTを作成するための抽象的なメソッドを使用して、テストケース自体を抽象化することです(つまり、テストケースはテンプレートメソッドデザインパターンを使用します)。


8

カスタム回答を使用してみてください。

例えば:

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class CustomAnswer implements Answer<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {

        Answer<Object> answer = null;

        if (isAbstract(invocation.getMethod().getModifiers())) {

            answer = Mockito.RETURNS_DEFAULTS;

        } else {

            answer = Mockito.CALLS_REAL_METHODS;
        }

        return answer.answer(invocation);
    }
}

抽象メソッドのモックを返し、具象メソッドの実際のメソッドを呼び出します。


5

抽象クラスのモックについて本当に私を気分悪くしているのは、デフォルトのコンストラクタYourAbstractClass()が呼び出されず(モックでsuper()が欠落している)、Mockitoでモックのプロパティをデフォルトで初期化する方法がない(リストプロパティなど)空のArrayListまたはLinkedList)。

私の抽象クラス(基本的にはクラスソースコードが生成されます)は、リスト要素の依存関係セッターインジェクションを提供せず、リスト要素を初期化するコンストラクター(手動で追加しようとしました)も提供しません。

クラス属性のみがデフォルトの初期化を使用します。privateList dep1 = new ArrayList; プライベートリストdep2 =新しいArrayList

したがって、実際のオブジェクト実装(ユニットテストクラスの内部クラス定義、抽象メソッドのオーバーライドなど)を使用せずに(適切なフィールド初期化を行う)実際のオブジェクトをスパイしない限り、抽象クラスをモックする方法はありません。

PowerMockだけがここでさらに役立つことは残念です。


2

テストクラスがテスト対象のクラスと同じパッケージ(別のソースルートの下)にあると想定すると、モックを簡単に作成できます。

YourClass yourObject = mock(YourClass.class);

他のメソッドと同じように、テストするメソッドを呼び出します。

superメソッドを呼び出す具体的なメソッドを期待して、呼び出される各メソッドに期待値を提供する必要があります。Mockitoでそれをどのように行うかはわかりませんが、EasyMockでそれが可能であると思います。

これYouClassにより、各抽象メソッドの具体的なインスタンスが作成され、空の実装を提供する手間が省けます。

余談ですが、私はテストで抽象クラスを実装すると便利なことがよくあります。これは、抽象クラスによって提供される機能に依存しますが、パブリックインターフェイスを介してテストする実装例として機能します。


3
しかし、モックを使用しても、YourClassの具象メソッドはテストされません。これは私が求めるものではありません。
ripper234 2009

1
正解です。抽象クラスで具象メソッドを呼び出したい場合、上記は機能しません。
Richard Nichols、

申し訳ありませんが、抽象的なものだけでなく、呼び出す各メソッドに必要な期待値について少し編集します。
Nick Holt

しかし、それでも具体的なメソッドではなく、モックをテストしています。
Jonatan Cloutier

2

テストでは、抽象クラスを匿名クラスで拡張できます。例(Junit 4を使用):

private AbstractClassName classToTest;

@Before
public void preTestSetup()
{
    classToTest = new AbstractClassName() { };
}

// Test the AbstractClassName methods.

2

Mockitoでは、@Mockアノテーションを使用して抽象クラスをモックすることができます。

public abstract class My {

    public abstract boolean myAbstractMethod();

    public void myNonAbstractMethod() {
        // ...
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Mock(answer = Answers.CALLS_REAL_METHODS)
    private My my;

    @Test
    private void shouldPass() {
        BDDMockito.given(my.myAbstractMethod()).willReturn(true);
        my.myNonAbstractMethod();
        // ...
    }
}

欠点は、コンストラクターパラメーターが必要な場合は使用できないことです。


0

匿名クラスをインスタンス化し、モックを注入してから、そのクラスをテストできます。

@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    @Mock
    MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {

            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

可視性は抽象クラスのprotectedプロパティのものmyDependencyServiceでなければならないことに注意してくださいClassUnderTest


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