【Java】MockitoのArgumentCaptorのテストの書き方
前置き
テストコードを書く際、多くの場合は「メソッドを動かした結果、正しい戻り値が返ってくるか」を assertEquals などで検証します。
しかし、下記の例のように、メソッドの戻り値だけでは正しさが証明できないことがあります。
- 戻り値がない(void)
- メソッド内で外部APIやリポジトリを呼び出しているが、詳細を返さずOKを返すだけ
- 複数の副作用がある
こうした、戻り値だけでは正しさを証明できない処理をテストするために必要なのが、「過程(振る舞い)」をテストすることです。
今回は、モックに渡した引数が正しいのかという引数部分に焦点を当てて解説します。
目的
過程を検証する目的としては、下記のような理由が挙げられます。
- 値の不変検証
- 重要な請求金額など、思わぬところで値が変わらず引数に引き渡せているか検証したい
- 値の変化検証
- 注文金額の計算など、値が想定通り変化したうえで引数に引き渡せているか検証したい
それでは、具体的なコードで使い方を紹介します。
テスト対象コード
注文を行うビジネスロジックです。
戻り値は void で、メソッドの中で注文金額を計算して注文を行います。
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
/**
* 注文を行う
* @param itemId 商品ID
* @param baseAmount 基準金額
*/
public void order(String itemId, int baseAmount) {
// 1000円以上購入時値引き
int discountedAmount = baseAmount >= 1000
? baseAmount - 100
: baseAmount;
orderRepository.addReceipt("値引き", discountedAmount);
// 消費税
int taxIncluded = (int) (discountedAmount * 1.1);
orderRepository.addReceipt("税", taxIncluded);
// 送料
int shoppingCostAmount = taxIncluded + 500;
orderRepository.addReceipt("送料", shoppingCostAmount);
// 注文
Order order = new Order(itemId, shoppingCostAmount);
orderRepository.save(order);
}
}
単一引数のテスト
注文金額の計算を行ったうえで、注文メソッドに引き渡した金額が想定通りかを検証します。
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@InjectMocks
private OrderService orderService;
@Mock
private OrderRepository orderRepository;
@Captor
private ArgumentCaptor<Order> orderCaptor;
@Test
void 注文時の注文金額が想定通りであること() {
// given
String itemId = "123-456";
int baseAmount = 1000;
// when
orderService.order(itemId, baseAmount);
// then
verify(orderRepository).save(orderCaptor.capture());
Order order = orderCaptor.getValue();
assertEquals("123-456", order.itemId());
// (1000 - 100) * 1.1 + 500
assertEquals(1490, order.amount());
}
}
複数引数のテストコード
注文金額の計算を行ったうえで、計算過程が想定通りか検証します。
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@InjectMocks
private OrderService orderService;
@Mock
private OrderRepository orderRepository;
@Captor
private ArgumentCaptor<Integer> amountCaptor;
@Test
void 注文時の注文金額の計算経過が想定通りであること() {
// given
String itemId = "123-456";
int baseAmount = 1000;
// when
orderService.order(itemId, baseAmount);
// then
verify(orderRepository, times(3)).addReceipt(anyString(), amountCaptor.capture());
List<Integer> amounts = amountCaptor.getAllValues();
assertEquals(List.of(
900, // 値引き済み
990, // 消費税込み
1490 // 送料込み
), amounts);
}
}
引数の検証
@Captor private ArgumentCaptor<キャプチャしたい引数の型> argumentCaptor;
キャプチャしたい引数の型をジェネリクスに指定して、ArgumentCaptorを定義します。
verify(モック, times(呼び出し回数)).検証したいメソッド(argumentCaptor.capture());
のように検証すると、検証したいメソッドに渡した引数をamountCaptorから取得できます。
一度しか実行してなければ argumentCaptor.getValue() で取得できますし、
複数回実行しているなら argumentCaptor.getAllValues() で取得できます。
まとめ
ArgumentCaptorをいつどういう理由で使うべきかをすぐ答えられなかったので、頭の中の整理を込めてまとめてみました。
assertEqualsと比べると出現頻度は少ないですが、ArgumentCaptorも重要なテストですね。
以上、ここまで見ていただきありがとうございます。
皆さまの快適な開発ライフに、ほんの少しでもお役に立てれば幸いです。