抽象クラスをテストしたいと思います。もちろん、クラスを継承する 手動でモックを書く ができます。
モックを手作りする代わりに、モックフレームワーク(Mockitoを使用)を使用してこれを行うことはできますか?どうやって?
次の提案では、「本当の」サブクラス-Mock isを作成せずに抽象クラスをテストできます。
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());
}
}
注:このソリューションの利点は、抽象メソッドが呼び出されない限り、haveを使用して抽象メソッドを実装しないことです。
私の正直な意見では、スパイはインスタンスを必要とするため、これはスパイを使用するよりも適切です。つまり、抽象クラスのインスタンス化可能なサブクラスを作成する必要があります。
アブストラクトに触れずに具体的なメソッドをテストする必要がある場合は、CALLS_REAL_METHODS
を使用できます( Morten's answer を参照)が、テスト中の具体的なメソッドが抽象、または実装されていないインターフェイスメソッド、これは動作しません-Mockitoは「Javaインターフェイスで実際のメソッドを呼び出せません」と文句を言います。
はい
回避策は、このアプローチを逆にすることです-通常の模擬動作(つまり、すべてが模擬/スタブ)を使用し、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を作成する抽象メソッドを使用して、テストケース自体を抽象化することです(言い換えると、テストケースは Template Method デザインパターンを使用します)。
カスタム回答を使用してみてください。
例えば:
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)。
私の抽象クラス(基本的にはクラスソースコードが生成されます)は、リスト要素の依存関係セッターインジェクションも、リスト要素を初期化するコンストラクターも提供しません(手動で追加しようとしました)。
クラス属性のみがデフォルトの初期化を使用します。private List dep1 = new ArrayList;プライベートリストdep2 =新しいArrayList
そのため、実際のオブジェクト実装(ユニットテストクラスの内部クラス定義、抽象メソッドのオーバーライドなど)を使用せずに実際のオブジェクトをスパイ(適切なフィールド初期化を行う)せずに、抽象クラスをモックする方法はありません。
ここでさらに役立つのはPowerMockだけです。
テストでは、抽象クラスを匿名クラスで拡張できます。例(Junit 4を使用):
private AbstractClassName classToTest;
@Before
public void preTestSetup()
{
classToTest = new AbstractClassName() { };
}
// Test the AbstractClassName methods.
テストクラスがテスト対象のクラスと同じパッケージ(異なるソースルートの下)にあると仮定すると、単純にモックを作成できます。
YourClass yourObject = mock(YourClass.class);
他のメソッドと同様に、テストするメソッドを呼び出します。
スーパーメソッドを呼び出す具体的なメソッドの期待値で呼び出される各メソッドに期待値を提供する必要があります-Mockitoでそれをどのように行うかはわかりませんが、EasyMockでは可能だと思います。
これは、YouClass
の具体的なインスタンスを作成し、各抽象メソッドの空の実装を提供する手間を省くだけです。
余談ですが、抽象クラスをテストに実装すると役立つことがよくあります。これは、パブリックインターフェイスを介してテストする実装例として機能しますが、これは抽象クラスによって提供される機能に依存します。
この場合、Whitebox.invokeMethod(..)は便利です。
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
でなければならないことに注意してください。