【Java】Mockitoのverifyのテストの書き方
前置き
テストコードを書く際、多くの場合は「メソッドを動かした結果、正しい戻り値が返ってくるか」を assertEquals などで検証します。
しかし、下記の例のように、メソッドの戻り値だけでは正しさが証明できないことがあります。
- 戻り値がない(void)
- メソッド内で外部APIやリポジトリを呼び出しているが、詳細を返さずOKを返すだけ
- 複数の副作用がある
こうした、戻り値だけでは正しさを証明できない処理をテストするために必要なのが、「過程(振る舞い)」をテストすることです。
今回は、メソッドの呼び出し回数や呼び出し順序に焦点を当てて解説します。
目的
過程を検証する目的としては、下記のような理由が挙げられます。
- 副作用(DB保存や通知)の担保
- 適切な処理を順番通りに呼び出せているか検証したい
- 多重処理の監視
- 決済APIなど、バグによって2回以上呼び出されていないこと検証したい
- ループやリトライ処理など、想定の回数実行されているかを検証したい
それでは、具体的なコードで使い方を紹介します。
呼び出し回数・手順の検証
verify は、テスト対象の処理の中で「モック化したコンポーネントが、意図通りに呼び出されたか」という回数や順序を検証するために使います。
verify テスト対象コード
注文の支払いを行うサンプルロジックです。
戻り値は void で、内部で主に「支払い」と「注文状況の更新」の2つを行っています。
public class PaymentUseCase {
private final OrderService orderService;
private final PaymentService paymentService;
public PaymentUseCase(OrderService orderService, PaymentService paymentService) {
this.orderService = orderService;
this.paymentService = paymentService;
}
/**
* 支払いを行う
* @param orderId 注文ID
*/
public void payment(OrderId orderId) {
// 支払い済みであれば支払いせず終了
if (orderService.isPaid(orderId)) {
return;
}
// 支払い
paymentService.payment(orderId);
// 注文状況を支払い済みに更新
orderService.payment(orderId);
}
}
このコードを verify を使って過程をテストします。
verify テストコード
@ExtendWith(MockitoExtension.class)
class PaymentUseCaseTest {
@InjectMocks
private PaymentUseCase paymentUseCase;
@Mock
private OrderService orderService;
@Mock
private PaymentService paymentService;
@Test
void 支払い済みだと支払処理が行われないこと() {
// given
OrderId orderId = mock(OrderId.class);
when(orderService.isPaid(orderId)).thenReturn(true);
// when
paymentUseCase.payment(orderId);
// then
verify(orderService, times(1)).isPaid(orderId);
verify(paymentService, never()).payment(orderId);
verify(orderService, never()).payment(orderId);
}
@Test
void 支払いに失敗すると注文状況処理は行われていないこと() {
// given
OrderId orderId = mock(OrderId.class);
when(orderService.isPaid(orderId)).thenReturn(false);
doThrow(RuntimeException.class).when(paymentService).payment(orderId);
// when&then
assertThrows(RuntimeException.class, () -> paymentUseCase.payment(orderId));
verify(orderService, times(1)).isPaid(orderId);
verify(paymentService, times(1)).payment(orderId);
verify(orderService, never()).payment(orderId);
}
@Test
void 支払いに成功すると注文状況処理まで行われること() {
// given
OrderId orderId = mock(OrderId.class);
when(orderService.isPaid(orderId)).thenReturn(false);
// when
paymentUseCase.payment(orderId);
// then
verify(orderService, times(1)).isPaid(orderId);
verify(paymentService, times(1)).payment(orderId);
verify(orderService, times(1)).payment(orderId);
InOrder inOrder = inOrder(orderService, paymentService);
inOrder.verify(paymentService).payment(orderId);
inOrder.verify(orderService).payment(orderId);
}
}
回数の検証
verify(モック, times(回数)).回数を検証したいメソッド(引数);
のように検証します。
一度も呼び出されていないかを検証する場合、 never() か times(0) を指定します。
※意味は一緒です
順序の検証
今回特に重要な部分として、支払い処理が正常に終了したのちに、DBを更新したい!
という要件だった場合、必ず支払い処理→DB更新の順番で処理を行う必要があります。
そういう順序が大切な場合にInOrderを使います。
InOrder inOrder = inOrder(モック1, モック2 ...); で検証したいモックを引数で渡します。
後は実行してほしい順番で検証コードを記述します。
inOrder.verify(モック).順序を検証したいメソッド(引数);
まとめ
verifyをいつどういう理由で使うべきかをすぐ答えられなかったので、頭の中の整理を込めてまとめてみました。
assertEqualsと比べると出現頻度は少ないですが、verifyも重要なテストですね。
以上、ここまで見ていただきありがとうございます。
皆さまの快適な開発ライフに、ほんの少しでもお役に立てれば幸いです。