Java Mock テストフレームワーク Mockito
Mock テストとは?
ソフトウェア開発において、単体テストはコードの機能を検証する重要な手段です。しかし、コードが外部システム(データベース、ネットワークサービスなど)に依存している場合、直接テストを行うと以下のようないくつかの問題に直面します:
- 外部依存が不安定または利用できない可能性がある
- テストの実行速度が遅くなる
- さまざまな境界条件をシミュレートするのが難しい
Mock テスト(シミュレーションテスト)は、オブジェクトの「代役」を作成することでこれらの問題を解決します。Mock オブジェクトは次のことができます:
- 実際のオブジェクトの動作をシミュレートする
- 相互作用が期待どおりに発生したかを検証する
- 実際のオブジェクトの実ロジックを実行しない
なぜ Mockito を選ぶのか?
Mockito は現在 Java エコシステムで最も人気のある Mock テストフレームワークであり、以下のような利点があります:
- シンプルな API:学習曲線が緩やかで、簡単に使い始められる
- 強力な機能:メソッド呼び出しの検証、戻り値の設定、例外のスローなどをサポート
- 優れた可読性:テストコードが直感的で分かりやすい
- 活発なコミュニティ:継続的な更新とメンテナンス、充実したドキュメント
Mockito の核心概念
1. Mock オブジェクトの作成
例
// 方法1:静的メソッドを使用
List mockedList = Mockito.mock(List.class);
// 方法2:アノテーションを使用(MockitoJUnitRunner と組み合わせる必要あり)
@Mock
List mockedList;
List mockedList = Mockito.mock(List.class);
// 方法2:アノテーションを使用(MockitoJUnitRunner と組み合わせる必要あり)
@Mock
List mockedList;
2. メソッドの動作を設定
例
// mockedList.size() が呼び出されたときに 100 を返す
when(mockedList.size()).thenReturn(100);
// mockedList.get(0) が呼び出されたときに "first" を返す
when(mockedList.get(0)).thenReturn("first");
// mockedList.get(1) が呼び出されたときに例外をスローする
when(mockedList.get(1)).thenThrow(new RuntimeException());
when(mockedList.size()).thenReturn(100);
// mockedList.get(0) が呼び出されたときに "first" を返す
when(mockedList.get(0)).thenReturn("first");
// mockedList.get(1) が呼び出されたときに例外をスローする
when(mockedList.get(1)).thenThrow(new RuntimeException());
3. 相互作用の検証
例
// mockedList.add("one") が1回呼び出されたことを検証
verify(mockedList).add("one");
// mockedList.clear() が一度も呼び出されていないことを検証
verify(mockedList, never()).clear();
// mockedList.add("two") が少なくとも2回呼び出されたことを検証
verify(mockedList, atLeast(2)).add("two");
verify(mockedList).add("one");
// mockedList.clear() が一度も呼び出されていないことを検証
verify(mockedList, never()).clear();
// mockedList.add("two") が少なくとも2回呼び出されたことを検証
verify(mockedList, atLeast(2)).add("two");
Mockito の実践的な応用例
ユーザーサービスのテスト
次のようなものがあるとしますUserServiceに依存するUserRepository:
例
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
}
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
}
Mockito を使用してテストする:
例
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testGetUserById_Success() {
// テストデータを準備
User mockUser = new User(1L, "testUser");
// Mock の動作を設定
when(userRepository.findById(1L))
.thenReturn(Optional.of(mockUser));
// テストを実行
User result = userService.getUserById(1L);
// 結果を検証
assertEquals("testUser", result.getUsername());
verify(userRepository).findById(1L);
}
@Test(expected = UserNotFoundException.class)
public void testGetUserById_NotFound() {
when(userRepository.findById(2L))
.thenReturn(Optional.empty());
userService.getUserById(2L);
}
}
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testGetUserById_Success() {
// テストデータを準備
User mockUser = new User(1L, "testUser");
// Mock の動作を設定
when(userRepository.findById(1L))
.thenReturn(Optional.of(mockUser));
// テストを実行
User result = userService.getUserById(1L);
// 結果を検証
assertEquals("testUser", result.getUsername());
verify(userRepository).findById(1L);
}
@Test(expected = UserNotFoundException.class)
public void testGetUserById_NotFound() {
when(userRepository.findById(2L))
.thenReturn(Optional.empty());
userService.getUserById(2L);
}
}
Mockito の高度な機能
1. 引数マッチャー
例
// 任意の整数引数
when(mockedList.get(anyInt())).thenReturn("element");
// 特定の型の引数
when(mockedList.contains(anyString())).thenReturn(true);
// カスタムマッチャー
when(mockedList.add(argThat(arg -> arg.length() > 5))).thenReturn(true);
when(mockedList.get(anyInt())).thenReturn("element");
// 特定の型の引数
when(mockedList.contains(anyString())).thenReturn(true);
// カスタムマッチャー
when(mockedList.add(argThat(arg -> arg.length() > 5))).thenReturn(true);
2. 呼び出し順序の検証
例
InOrder inOrder = inOrder(mockedList);
inOrder.verify(mockedList).add("first");
inOrder.verify(mockedList).add("second");
inOrder.verify(mockedList).add("first");
inOrder.verify(mockedList).add("second");
3. 部分 Mock (Spy)
例
List realList = new ArrayList();
List spyList = spy(realList);
// 実際のメソッドを呼び出す
spyList.add("real");
// 特定のメソッドをモックする
doReturn(100).when(spyList).size();
List spyList = spy(realList);
// 実際のメソッドを呼び出す
spyList.add("real");
// 特定のメソッドをモックする
doReturn(100).when(spyList).size();
ベストプラクティス
- Mock を使いすぎない:必要な依存関係のみ Mock 化し、テストの現実性を保つ
- 相互作用の検証は適度に:重要な相互作用に注目し、すべてのメソッド呼び出しを検証しない
- テストを簡潔に保つ:各テストメソッドは1つの機能ポイントのみをテストするべきです
- テストコードを適切に整理する:@Before を使用して共通設定を行う
- 他のテストツールと組み合わせる:JUnit、AssertJ などと組み合わせて使用する
よくある質問(FAQ)
Q: Mockito と PowerMock の違いは何ですか?
A: Mockito は主に通常のオブジェクトの Mock に使用されますが、PowerMock は静的メソッドやコンストラクタなどを Mock できます。ただし、PowerMock はテストの分離性を損なうため、優先的に Mockito を使用することをお勧めします。
Q: Mock ではなく Spy はいつ使うべきですか?
A: ほとんどの実際の動作が必要で、少数のメソッドだけを変更したい場合は Spy を使用します。オブジェクトの動作を完全に制御する必要がある場合は Mock を使用します。
Q: Mockito は final クラスやメソッドに使用できますか?
A: Mockito 2.1.0 以降、設定により final クラスやメソッドを Mock できますが、追加する必要がありますmockito-inline依存関係。
Java よく使うクラスライブラリ