Mockitoスパイを使用するためのユースケースは何でしょうか?
すべてのスパイのユースケースは、callRealMethodを使用してモックで処理できるように思われます。
私が見ることができる1つの違いは、ほとんどのメソッド呼び出しを実際のものにしたい場合、モックとスパイを使用するためにコードの数行を節約することです。それはそれですか、それとも私は全体像を見逃していますか?
スパイとモックの違い
Mockitoがモックを作成する場合、実際のインスタンスからではなく、型のクラスから作成します。モックは、クラスの必要最低限のシェルインスタンスを作成するだけで、クラスとの相互作用を追跡するために完全にインストルメント化されています。一方、スパイは既存のインスタンスをラップします。それでも通常のインスタンスと同じように動作します。唯一の違いは、それとのすべての相互作用を追跡するようにインストルメント化されることです。
次の例では、ArrayListクラスのモックを作成します。
@Test
public void whenCreateMock_thenCreated() {
List mockedList = Mockito.mock(ArrayList.class);
mockedList.add("one");
Mockito.verify(mockedList).add("one");
assertEquals(0, mockedList.size());
}
ご覧のとおり、モックリストに要素を追加しても実際には何も追加されません。他の副作用なしにメソッドを呼び出すだけです。一方、スパイの動作は異なります。実際には、addメソッドの実際の実装を呼び出し、要素を基になるリストに追加します。
@Test
public void whenCreateSpy_thenCreate() {
List spyList = Mockito.spy(new ArrayList());
spyList.add("one");
Mockito.verify(spyList).add("one");
assertEquals(1, spyList.size());
}
ここでは、size()メソッドを呼び出すとサイズが1になるため、オブジェクトの実際の内部メソッドが呼び出されたと確実に言えますが、このsize()メソッドはモックされていません。では、1はどこから来るのでしょうか?内部のrealsize()メソッドは、size()がモック(またはスタブ)されていないために呼び出されます。したがって、エントリが実オブジェクトに追加されたと言えます。
出典:http ://www.baeldung.com/mockito-spy+ セルフノート。
答えはドキュメントにあります:
実際の部分的なモック(1.8.0以降)
最後に、メーリングリストでの多くの内部討論と議論の後、部分的なモックサポートがMockitoに追加されました。以前は、部分的なモックをコードの臭いと見なしていました。ただし、部分的なモックの正当な使用例が見つかりました。
リリース1.8以前は、spy()は実際の部分的なモックを生成しておらず、一部のユーザーにとっては混乱を招きました。スパイの詳細については、こちらまたはjavadocでspy(Object)メソッドを参照してください。
callRealMethod()
の後に導入されましたspy()
が、下位互換性を確保するために、もちろんspy()はそこに残されています。
そうでなければ、あなたは正しいです:スタブされない限り、スパイのすべての方法は本物です。callRealMethod()
が呼び出されない限り、モックのすべてのメソッドはスタブされます。一般的に、従来のイディオムの代わりにイディオムcallRealMethod()
を使用する必要がないため、を使用することをお勧めしますdoXxx().when()
when().thenXxx()
8つのメソッドを持つオブジェクトがあり、7つの実際のメソッドを呼び出し、1つのメソッドをスタブするテストがある場合、2つのオプションがあります。
spy
1つの方法をスタブして設定する必要がありますの公式ドキュメントでdoCallRealMethod
は、部分的なモックにスパイを使用することを推奨しています。
部分的なモックの詳細については、javadoc spy(Object)も参照してください。Mockito.spy()は、部分的なモックを作成するための推奨される方法です。その理由は、spy()メソッドに渡されるオブジェクトを作成する責任があるため、正しく作成されたオブジェクトに対して実際のメソッドが呼び出されることが保証されるためです。
スパイは、レガシーコードの単体テストを作成する場合に役立ちます。
ここに実行可能な例を作成しましたhttps://www.surasint.com/mockito-with-spy/、ここにその一部をコピーします。
このコードのようなものがある場合:
public void transfer( DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService,
double amount, String fromAccount, String toAccount){
withdrawMoneyService.withdraw(fromAccount,amount);
depositMoneyService.deposit(toAccount,amount);
}
DepositMoneyServiceとWithdrawMoneyServiceをモックするだけなので、スパイは必要ないかもしれません。
しかし、一部のレガシーコードでは、依存関係は次のようなコードにあります。
public void transfer(String fromAccount, String toAccount, double amount){
this.depositeMoneyService = new DepositMoneyService();
this.withdrawMoneyService = new WithdrawMoneyService();
withdrawMoneyService.withdraw(fromAccount,amount);
depositeMoneyService.deposit(toAccount,amount);
}
はい、最初のコードに変更できますが、その後APIが変更されます。この方法が多くの場所で使用されている場合は、すべてを変更する必要があります。
別の方法は、次のように依存関係を抽出できることです。
public void transfer(String fromAccount, String toAccount, double amount){
this.depositeMoneyService = proxyDepositMoneyServiceCreator();
this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();
withdrawMoneyService.withdraw(fromAccount,amount);
depositeMoneyService.deposit(toAccount,amount);
}
DepositMoneyService proxyDepositMoneyServiceCreator() {
return new DepositMoneyService();
}
WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
return new WithdrawMoneyService();
}
次に、スパイを使用して、次のように依存関係を挿入できます。
DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);
TransferMoneyService target = spy(new TransferMoneyService());
doReturn(mockDepositMoneyService)
.when(target).proxyDepositMoneyServiceCreator();
doReturn(mockWithdrawMoneyService)
.when(target).proxyWithdrawMoneyServiceCreator();
上記のリンクで詳細をご覧ください。
Mock
vsSpy
Mock
裸の二重オブジェクトです。このオブジェクトには同じメソッドシグネチャがありますが、実現は空であり、デフォルト値-0およびnullを返します
Spy
複製されたdoubleオブジェクトです。新しいオブジェクトは実際のオブジェクトに基づいて複製されますが、それをモックする可能性があります
class A {
String foo1() {
foo2();
return "RealString_1";
}
String foo2() {
return "RealString_2";
}
void foo3() { foo4(); }
void foo4() { }
}
@Test
public void testMockA() {
//given
A mockA = Mockito.mock(A.class);
Mockito.when(mockA.foo1()).thenReturn("MockedString");
//when
String result1 = mockA.foo1();
String result2 = mockA.foo2();
//then
assertEquals("MockedString", result1);
assertEquals(null, result2);
//Case 2
//when
mockA.foo3();
//then
verify(mockA).foo3();
verify(mockA, never()).foo4();
}
@Test
public void testSpyA() {
//given
A spyA = Mockito.spy(new A());
Mockito.when(spyA.foo1()).thenReturn("MockedString");
//when
String result1 = spyA.foo1();
String result2 = spyA.foo2();
//then
assertEquals("MockedString", result1);
assertEquals("RealString_2", result2);
//Case 2
//when
spyA.foo3();
//then
verify(spyA).foo3();
verify(spyA).foo4();
}