unit-test-service-layer について
MockitoでJavaのサービス層をテストする開発者向けの実践ガイドです。依存関係のモック化、呼び出し検証、例外ケースを含む、`@Service` クラスの独立したテスト方法を扱います。
主な機能
- 01MockitoExtensionによるテスト設定
- 02リポジトリとクライアントのモック化
- 03結果と依存先呼び出しの検証
- 04例外とエッジケースのテスト
- 05テスト実行とカバレッジ確認
活用シーン
- 01@Serviceのビジネスロジックを単独でテスト
- 02サービスからリポジトリへの呼び出しを確認
- 03外部APIの失敗処理をテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill unit-test-service-layerインストール後、Claude Code で/unit-test-service-layerを入力して読み込まれたか確認します。
同梱ファイルreferences/examples.md
詳細
Mockito を使用したサービス層の単体テスト
概要
Mockito を使用して @Service クラスを単体テストするためのパターンを紹介します。リポジトリー呼び出しをモックし、メソッドの呼び出しを検証し、例外のシナリオをテストして、外部 API のレスポンスをスタブ化します。Spring コンテナーやデータベースを使わずに、高速で分離されたテストを実行できます。
使用する場面
@Serviceクラスのビジネスロジックをテストする- リポジトリーや外部クライアントの依存関係をモックする
- モック化したコラボレーターとのサービスのやり取りを検証する
- サービスのエラー処理やエッジケースをテストする
- 高速で分離された単体テストを作成する(データベースや API 呼び出しは不要)
手順
検証チェックポイントを含む、Mockito を使用したサービス層のテストでは、次のワークフローに従ってください。
1. テストクラスをセットアップする
@ExtendWith(MockitoExtension.class) を使用して、Mockito のアノテーションを有効にします。
2. @Mock と @InjectMocks でモックを宣言する
依存関係(リポジトリー、クライアント)には @Mock を使用し、テスト対象のサービスには @InjectMocks を使用します。
3. 検証を含む Arrange-Act-Assert
Arrange:テストデータを作成し、when().thenReturn() を使用してモックの戻り値を設定します。
Act:テスト対象のサービスメソッドを実行します。
Assert:
- AssertJ のアサーションで戻り値を検証します
verify()でモックとのやり取りを検証します- 検証チェックポイント:テストを実行し、成功することを確認します
4. 例外のシナリオをテストする
when().thenThrow() を使用して、モックが例外をスローするように設定します。
検証チェックポイント:例外の型とメッセージを確認します
5. 網羅性を確認する
- テストスイート全体を実行します:
mvn testまたはgradle test - カバレッジレポートを確認します:
mvn test jacoco:report - 検証チェックポイント:すべてのサービスメソッドに対応するテストがあることを確認します
例
基本的なサービスのテストパターン
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void shouldReturnUserWhenFound() {
// Arrange
User expected = new User(1L, "Alice");
when(userRepository.findById(1L)).thenReturn(Optional.of(expected));
// Act
User result = userService.getUser(1L);
// Assert
assertThat(result.getName()).isEqualTo("Alice");
verify(userRepository).findById(1L);
}
@Test
void shouldThrowWhenUserNotFound() {
// Arrange
when(userRepository.findById(999L)).thenReturn(Optional.empty());
// Act & Assert
assertThatThrownBy(() -> userService.getUser(999L))
.isInstanceOf(UserNotFoundException.class);
}
}
メソッド呼び出しを検証する
@Test
void shouldSendEmailOnUserCreation() {
User newUser = new User(1L, "Alice", "[email protected]");
when(userRepository.save(any(User.class))).thenReturn(newUser);
enrichmentService.registerNewUser("Alice", "[email protected]");
verify(userRepository).save(any(User.class));
verify(emailService).sendWelcomeEmail("[email protected]");
}
複数の依存関係、引数キャプター、非同期サービス、InOrder による検証など、その他のパターンについては、references/examples.md を参照してください。
ベストプラクティス
- JUnit 5 との統合には
@ExtendWith(MockitoExtension.class)を使用する - テスト対象のサービスが直接依存するものだけをモックする
- 適切な連携を確認するために やり取りを検証する
- テストメソッドごとに 1 つの動作をテストする - テストの焦点を絞ります
- わかりやすい変数名を使用する:
expectedUser、actualUser、captor - 値オブジェクトや DTO には 実際のインスタンスを作成する(モック化しないでください)
制約と注意事項
- 値オブジェクトや DTO はモック化せず、テストデータを使って実際のインスタンスを作成してください。
- 依存関係を過剰にモック化しないでください。サービスにコラボレーターが多すぎる場合は、リファクタリングを検討してください。
- テストは独立している必要があります。実行順序に依存しないでください。
@Spyの使用には注意してください。部分的なモック化は理解や保守がより難しくなります。- private メソッドを直接テストせず、public メソッドの動作を通じてテストしてください。
- 引数マッチャー(
any()、eq())は、同じスタブ内で実際の値と混在させることはできません。 - 過剰な検証は避け、テストシナリオで重要なやり取りのみを検証してください。