単体テストを書いていて、「引数に応じた戻り値を返したい」「一部だけ実際のオブジェクトに処理を任せたい」「遅延をシミュレートしたい」といった場面に遭遇したことはありませんか?
こうした柔軟なモックの振る舞いを簡潔に実現できるのが、Mockitoのユーティリティクラス AdditionalAnswers
です。
この記事では、AdditionalAnswers
が提供する代表的な機能を、具体的なコード例とともに分かりやすく解説します。
テストコードの表現力を高めたい方、よりリアルなユニットテストを目指したい方にとって、きっと役立つ内容です。
AdditionalAnswersとは?
AdditionalAnswers
は、Mockitoが提供する補助的なユーティリティクラスで、thenAnswer()
に渡すための便利な Answer
インタフェースの実装を多数提供しています。
Mockitoでは通常、thenReturn(...)
や thenThrow(...)
を使ってモックの戻り値や例外を設定しますが、
「引数に応じた戻り値を返す」「呼び出しごとに異なる動作をさせる」といったより動的な振る舞いを実現したい場合は、thenAnswer(...)
を使う必要があります。
その際に毎回カスタム Answer
を自前で書くのは面倒ですが、AdditionalAnswers
を使えば、よくあるパターンを簡潔に表現できます。
可読性の高いテストコードを書きたいときに、非常に便利なユーティリティです。
AdditionalAnswersの使い方の基本形
通常は static import
を使って以下のようにインポートするのが一般的です。
import static org.mockito.AdditionalAnswers.*;
すべてのメソッドは Answer<T>
を返すため、thenAnswer(...)
に渡す形で使用します。
when(mock.method(...)).thenAnswer(returnsFirstArg());
代表的なメソッドと使用例
AdditionalAnswers
は、モックの柔軟な振る舞いを簡潔に記述できるようにするための便利な静的メソッド群です。以下に、主なメソッドとその用途、コード例を解説します。
1. returnsFirstArg()
:最初の引数をそのまま返す
when(service.echo(anyString())).thenAnswer(returnsFirstArg());
用途:
- 単純な「入力をそのまま返す」処理のモックに最適です。
- バリデーション、エコー系、同一値比較のテストに使われます。
2. returnsSecondArg()
:2番目の引数を返す
when(service.compare(any(), any())).thenAnswer(returnsSecondArg());
用途:
- 複数引数のうち、2つ目を使用するメソッドの簡易スタブ。
- 複雑な条件分岐が不要なときに使いやすいです。
3. returnsLastArg()
:最後の引数を返す
when(service.combine(any(), any(), any())).thenAnswer(returnsLastArg());
用途:
- 引数の数が可変で最後の引数が重要な場合に便利。
- 引数の数が固定でなくても使えるのが特徴。
4. returnsArgAt(int index)
:指定インデックスの引数を返す
when(service.process(any(), any(), any())).thenAnswer(returnsArgAt(1)); // 2番目の引数を返す
用途:
- 任意の位置の引数を返す汎用的なメソッド。(インデックスは0始まり)
-1
を指定した場合には、最後の引数を返す。
5. returnsElementsOf(Collection<?>)
:順番に値を返す
List<String> sequence = List.of("One", "Two", "Three");
when(service.next()).thenAnswer(returnsElementsOf(sequence));
用途:
- 呼び出し回数ごとに返す値を切り替えたい場合。
- ステートマシンや逐次応答のモックなどに便利。
6. answersWithDelay(long delayMillis, Answer<T>)
:遅延応答を模倣
when(service.delayedEcho(any())).thenAnswer(answersWithDelay(1000, returnsFirstArg()));
用途:
- 応答遅延を再現してタイムアウト処理や非同期処理のテスト。
- 複雑な環境再現が不要な疑似的パフォーマンステストに有効。
7. delegatesTo(Object realObject)
:実オブジェクトに処理を委譲
List<String> realList = new ArrayList<>();
List<String> mockList = mock(List.class, delegatesTo(realList));
用途:
- 実オブジェクトの動作を模倣しつつ、モックとして挙動を監視したいとき。
spy()
よりも安全(コンストラクタや副作用の問題を回避できる)。
注意点とベストプラクティス
AdditionalAnswers
は強力で便利なツールですが、使い方を誤るとテストの可読性や保守性を損なう可能性もあります。以下の点に注意しながら、効果的に活用しましょう。
多用しすぎない
AdditionalAnswers
を過剰に使うと、モックの振る舞いがブラックボックス化し、テストの意図が見えにくくなります。
シンプルに書けるケースに限定し、あくまで「読みやすさ」を優先しましょう。
設定の意図が伝わるようにする
特定の Answer
を使っている理由が読み手に伝わるかを意識しましょう。
可能であればコメントを添える、もしくは意図が明確になる命名や構造に工夫を加えると効果的です。
複雑な処理にはカスタム実装を使う
振る舞いが複雑になる場合は、無理に AdditionalAnswers
に頼るのではなく、ラムダ式や専用の Answer
実装で明示的に書いたほうが安全です。
テストが壊れたときの原因調査やデバッグの効率も上がります。
まとめ:Mockitoの柔軟性を引き出すAdditionalAnswers
AdditionalAnswers
を活用することで、引数や呼び出し順に応じたモックの振る舞いを簡潔に表現できるようになります。
これはテストコードの柔軟性と表現力を高め、より現実的なユニットテストの構築に大いに役立ちます。
ただし、便利だからといって何でも AdditionalAnswers
に頼るのは禁物です。
複雑な振る舞いはかえってテストを読みにくくすることがあるため、「読みやすさ」と「意図の明確さ」を常に意識することが重要です。
状況に応じてうまく取り入れることで、あなたのテストコードはより直感的で、保守しやすいものになるでしょう。
ぜひ今回紹介した使い方を参考に、Mockitoの可能性をさらに引き出してみてください。