回答:
次の提案では、「実際の」サブクラスを作成せずに抽象クラスをテストしましょう-モックはサブクラスです。
を使用してから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());
}
}
注:このソリューションの美しさは、あなたがいないということである持っている限り、彼らが呼び出されることはありませんよう、抽象メソッドを実装します。
私の正直な意見では、スパイはインスタンスを必要とするため、これはスパイを使用するよりも優れています。つまり、抽象クラスのインスタンス化可能なサブクラスを作成する必要があります。
アブストラクトに触れずにいくつかの具象メソッドをテストする必要がある場合は使用できますCALLS_REAL_METHODS
(Mortenの回答を参照)。ただし、テスト中の具象メソッドがいくつかのアブストラクトまたは実装されていないインターフェースメソッドを呼び出す場合、これは機能しません。 -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は「未完了のスタブが検出されました」と文句を言います。
これを行うには、スパイを使用します(ただし、最新バージョンの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()));
モックフレームワークは、テストするクラスの依存関係を簡単にモックアウトできるように設計されています。モックフレームワークを使用してクラスをモックすると、ほとんどのフレームワークはサブクラスを動的に作成し、メソッドの実装を、メソッドが呼び出されたときに検出して偽の値を返すコードに置き換えます。
抽象クラスをテストするときは、テスト対象(SUT)の非抽象メソッドを実行する必要があるため、モックフレームワークは必要ありません。
混乱の一部は、あなたがリンクした質問への答えが、あなたの抽象クラスから伸びるモックを手作りすることだと言ったことです。私はそのようなクラスをモックとは呼びません。モックは、依存関係の代わりとして使用されるクラスであり、期待値でプログラムされ、これらの期待値が満たされているかどうかを照会することができます。
代わりに、テストで抽象クラスの非抽象サブクラスを定義することをお勧めします。その結果、コードが多すぎる場合は、クラスの拡張が難しいことを示している可能性があります。
代替ソリューションは、SUTを作成するための抽象的なメソッドを使用して、テストケース自体を抽象化することです(つまり、テストケースはテンプレートメソッドデザインパターンを使用します)。
カスタム回答を使用してみてください。
例えば:
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);
}
}
抽象メソッドのモックを返し、具象メソッドの実際のメソッドを呼び出します。
抽象クラスのモックについて本当に私を気分悪くしているのは、デフォルトのコンストラクタYourAbstractClass()が呼び出されず(モックでsuper()が欠落している)、Mockitoでモックのプロパティをデフォルトで初期化する方法がない(リストプロパティなど)空のArrayListまたはLinkedList)。
私の抽象クラス(基本的にはクラスソースコードが生成されます)は、リスト要素の依存関係セッターインジェクションを提供せず、リスト要素を初期化するコンストラクター(手動で追加しようとしました)も提供しません。
クラス属性のみがデフォルトの初期化を使用します。privateList dep1 = new ArrayList; プライベートリストdep2 =新しいArrayList
したがって、実際のオブジェクト実装(ユニットテストクラスの内部クラス定義、抽象メソッドのオーバーライドなど)を使用せずに(適切なフィールド初期化を行う)実際のオブジェクトをスパイしない限り、抽象クラスをモックする方法はありません。
PowerMockだけがここでさらに役立つことは残念です。
テストクラスがテスト対象のクラスと同じパッケージ(別のソースルートの下)にあると想定すると、モックを簡単に作成できます。
YourClass yourObject = mock(YourClass.class);
他のメソッドと同じように、テストするメソッドを呼び出します。
superメソッドを呼び出す具体的なメソッドを期待して、呼び出される各メソッドに期待値を提供する必要があります。Mockitoでそれをどのように行うかはわかりませんが、EasyMockでそれが可能であると思います。
これYouClass
により、各抽象メソッドの具体的なインスタンスが作成され、空の実装を提供する手間が省けます。
余談ですが、私はテストで抽象クラスを実装すると便利なことがよくあります。これは、抽象クラスによって提供される機能に依存しますが、パブリックインターフェイスを介してテストする実装例として機能します。
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();
// ...
}
}
欠点は、コンストラクターパラメーターが必要な場合は使用できないことです。
匿名クラスをインスタンス化し、モックを注入してから、そのクラスをテストできます。
@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
。
SomeAbstract spy = spy(SomeAbstract.class);