Java Mock テストフレームワーク Mockito

Java 常用类库Java よく使うクラスライブラリ


Mock テストとは?

ソフトウェア開発において、単体テストはコードの機能を検証する重要な手段です。しかし、コードが外部システム(データベース、ネットワークサービスなど)に依存している場合、直接テストを行うと以下のようないくつかの問題に直面します:

  1. 外部依存が不安定または利用できない可能性がある
  2. テストの実行速度が遅くなる
  3. さまざまな境界条件をシミュレートするのが難しい

Mock テスト(シミュレーションテスト)は、オブジェクトの「代役」を作成することでこれらの問題を解決します。Mock オブジェクトは次のことができます:

  • 実際のオブジェクトの動作をシミュレートする
  • 相互作用が期待どおりに発生したかを検証する
  • 実際のオブジェクトの実ロジックを実行しない

なぜ Mockito を選ぶのか?

Mockito は現在 Java エコシステムで最も人気のある Mock テストフレームワークであり、以下のような利点があります:

  1. シンプルな API:学習曲線が緩やかで、簡単に使い始められる
  2. 強力な機能:メソッド呼び出しの検証、戻り値の設定、例外のスローなどをサポート
  3. 優れた可読性:テストコードが直感的で分かりやすい
  4. 活発なコミュニティ:継続的な更新とメンテナンス、充実したドキュメント

Mockito の核心概念

1. Mock オブジェクトの作成

例

// 方法1:静的メソッドを使用
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());

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");

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"));
    }
}

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);
    }
}

Mockito の高度な機能

1. 引数マッチャー

例

// 任意の整数引数
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");

3. 部分 Mock (Spy)

例

List realList = new ArrayList();
List spyList = spy(realList);

// 実際のメソッドを呼び出す
spyList.add("real");

// 特定のメソッドをモックする
doReturn(100).when(spyList).size();

ベストプラクティス

  1. Mock を使いすぎない:必要な依存関係のみ Mock 化し、テストの現実性を保つ
  2. 相互作用の検証は適度に:重要な相互作用に注目し、すべてのメソッド呼び出しを検証しない
  3. テストを簡潔に保つ:各テストメソッドは1つの機能ポイントのみをテストするべきです
  4. テストコードを適切に整理する:@Before を使用して共通設定を行う
  5. 他のテストツールと組み合わせる: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依存関係。


関連リンク

  1. Mockito 公式ドキュメント
  2. Mockito GitHub リポジトリ

Java 常用类库Java よく使うクラスライブラリ

その他の拡張