unit-test-scheduled-async について
Spring と JUnit 5 を使う開発者向けに、`@Scheduled` と `@Async` メソッドを分離してテストする方法を紹介します。実際のスケジュールを待たずに、非同期の結果や例外を検証できます。
主な機能
- 01メソッドを直接呼び出す
- 02CompletableFuture の結果確認
- 03Awaitility による条件待機
- 04非同期例外伝播のテスト
- 05タスク実行回数の検証
活用シーン
- 01スケジュールタスクのロジックテスト
- 02非同期メソッドの結果確認
- 03非同期エラー処理の検証
- 04待機なしでの cron ロジックテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill unit-test-scheduled-asyncインストール後、Claude Code で/unit-test-scheduled-asyncを入力して読み込まれたか確認します。
同梱ファイルreferences/examples.md
詳細
@Scheduled および @Async メソッドのユニットテスト
概要
JUnit 5 で Spring の @Scheduled および @Async メソッドをユニットテストするためのパターンです。CompletableFuture の結果をテストし、Awaitility を使って競合状態に対処し、スケジュールされたタスクの実行をモックし、実際のスケジュール間隔を待たずにエラー処理を検証します。
使用する場面
@Scheduledメソッドのロジックをテストする場合@Asyncメソッドの動作をテストする場合CompletableFutureの結果を検証する場合- 非同期エラー処理をテストする場合
- 実際のスケジュール実行を待たずに cron 式のロジックをテストする場合
- スレッドプールの動作と実行回数を検証する場合
- バックグラウンドタスクのロジックを分離してテストする場合
手順
@Asyncメソッドを直接呼び出します — Spring の非同期プロキシをバイパスします。ユニットテストではアノテーションは関係ありません- 依存関係をモックします —
@Mockと@InjectMocks(Mockito)を使用します - 完了を待ちます —
CompletableFuture.get(timeout, unit)またはawait().atMost(...).untilAsserted(...)を使用します @Scheduledメソッドを直接呼び出します — cron や fixedRate の実行を待たないでください。ユニットテストではアノテーションは無視されます- 例外経路をテストします —
CompletableFuture.get()でExecutionExceptionにラップされることを検証します
検証チェックポイント:
CompletableFuture.get()の後、モックの呼び出しを検証する前に、戻り値をアサートしますExecutionExceptionがスローされた場合は、.getCause()を確認して根本原因の例外を特定します- Awaitility がタイムアウトした場合は、条件が満たされるまで
atMost()の時間を延ばすか、pollInterval()を短くします - タスクを複数回実行した後、
verify()を呼び出す前に実行回数をアサートします
例
主なパターン — 完全な例は references/examples.md を参照してください。
// @Async: call directly, wait with CompletableFuture.get(timeout, unit)
@Service
class EmailService {
@Async
public CompletableFuture<Boolean> sendEmailAsync(String to) {
return CompletableFuture.supplyAsync(() -> true);
}
}
@Test
void shouldReturnCompletedFuture() throws Exception {
EmailService service = new EmailService();
Boolean result = service.sendEmailAsync("[email protected]").get(5, TimeUnit.SECONDS);
assertThat(result).isTrue();
}
// @Scheduled: call directly, mock the repository
@Component
class DataRefreshTask {
@InjectMocks private DataRepository dataRepository;
@Scheduled(fixedDelay = 60000) public void refreshCache() { /* ... */ }
}
@Test
void shouldRefreshCache() {
when(dataRepository.findAll()).thenReturn(List.of(new Data(1L, "item1")));
dataRefreshTask.refreshCache();
verify(dataRepository).findAll();
}
// Awaitility: use for race conditions with shared mutable state
@Test
void shouldProcessAllItems() {
BackgroundWorker worker = new BackgroundWorker();
worker.processItems(List.of("item1", "item2", "item3"));
Awaitility.await()
.atMost(Duration.ofSeconds(5))
.pollInterval(Duration.ofMillis(100))
.untilAsserted(() -> assertThat(worker.getProcessedCount()).isEqualTo(3));
}
// Mocked dependencies with exception handling
@Test
void shouldHandleAsyncExceptionGracefully() {
doThrow(new RuntimeException("Email failed")).when(emailService).send(any());
CompletableFuture<String> result = service.notifyUserAsync("user123");
assertThatThrownBy(result::get)
.isInstanceOf(ExecutionException.class)
.hasCauseInstanceOf(RuntimeException.class);
}
Maven/Gradle の依存関係一式、追加のテストクラス、実行回数のパターンについては、references/examples.md を参照してください。
ベストプラクティス
- テストが停止しないよう、
CompletableFuture.get()には必ずタイムアウトを設定します - すべての依存関係をモックします — ユニットテストで実際の外部サービスを呼び出さないでください
- Awaitility は競合状態に対処する場合にのみ使用し、単純な非同期メソッドでは直接呼び出しを優先します
@Scheduledのロジックを直接テストします — ユニットテストではアノテーションは無視されます- モックの呼び出しを検証する前に値をアサートし、非同期処理の完了後に検証します
よくある落とし穴
- メソッドを直接呼び出さず、Spring の非同期エグゼキューターに依存する
CompletableFuture.get()にタイムアウトを設定し忘れる- 非同期メソッドでの例外の伝播をテストし忘れる
- 非同期メソッドが内部で呼び出す依存関係をモックしない
- ロジックを分離してテストせず、実際の cron や fixedRate のタイミングを待つ
制約と警告
@Asyncの自己呼び出し:同じクラス内の別のメソッドから@Asyncを呼び出すと、同期的に実行されます。Spring のプロキシがバイパスされるためです- スレッドプールの実行順序:
ThreadPoolTaskSchedulerは実行順序を保証しません - CompletableFuture のチェーン:中間段階で発生した例外が黙って失われる場合があります。各段階をテストしてください
- Awaitility のタイムアウト:常に妥当な
atMost()を設定してください。無期限の待機はテストスイートを停止させます - 実際のスケジューリングは行われません:ユニットテストでは
@Scheduledは無視されます。メソッドを直接呼び出してください
参考資料
- Spring の
@Asyncドキュメント - Spring の
@Scheduledドキュメント - Awaitility テストライブラリ
- CompletableFuture API
- コード例:
references/examples.md
コメント
Markdown 対応