fake_asyncは、時間の経過監視などの非同期処理のテストを行うためパッケージです。ログを日付でローテションさせるrotation_logパッケージを作ったのですが、そこでテストをしていて、少しはまったので、メモ代わりに書きます。
日付を使ったテスト
時間経過をテストするためには、clockのパッケージを使って時間の取得する部分を書き換えます。標準のDateTimeから、clockに変更します。
final now = DateTime.now()
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
final now = clock.now()
テストではFakeAsyncのinitialTime
に初期値を設定すると、run内の関数ブロックでは、clock.now()で定義した日付が返るようになります。
FakeAsync(initialTime: DateTime(2000, 1, 1)).run((_) {
final current = clock.now();
print(DateFormat.yMd().format(current)); // 1/1/2000
});
初期値を変えるだけでなく、時間を経過したテストもできます。FakeAsyncのインスタンスが渡されるので、日付の進行をテストできます。
FakeAsync(initialTime: DateTime(2000, 1, 1)).run((fake) {
print(DateFormat.yMd().format(clock.now())); // 1/1/2000
fake.elapse(const Duration(days: 1)); // 1日進める
print(DateFormat.yMd().format(clock.now())); // 1/2/2000
});
非同期処理でfake_asyncを使いたい
ここからが本題です。ファイルを開いたり、ディレクトリのパスを読み取るなど非同期で行われる処理があります。今回、ログのローテーション処理のテストで、時間を経過していたら、ファイルを整理するテストを書きたかったのですが、うまくいきませんでした。
以下の場合、処理は返ってきません。fake_asyncは非同期処理を書き換えてテストするためのパッケージなので、正規の処理を実行できませんでした。
test('microtask', () async {
await FakeAsync().run((_) async {
final v = await Future.microtask(() => 1);
expect(v, 1);
});
});
FakeAsync内はasyncを使わず、そのまま流れるように処理を書きます。
test('microtask', () {
FakeAsync().run((fakeAsync) {
var v = 0;
Future.microtask(() => v = 1);
fakeAsync.flushMicrotasks();
expect(v, 1);
});
});
flushMicrotasksを呼び出すと、積まれているmicrotaskを全て消化するように処理が走ります。この辺りいろんなパターンで、テストが書かれているので、参考になると思います。
非同期処理の中で時間経過だけをテストしたい場合
今回、現在の時間の経過だけできればよかったので、fake_asyncは不要でした。clockのwithClock関数を使います。Clockに時間を返す関数を定義するのですが、ここに差分を計算した日付を返すようにすれば、時間の経過をwithClock内で適用できます。
fake_asyncのパッケージ内で同じような処理があり、参考にしています。
final now = DateTime(2000, 1, 1);
var elapsed = Duration.zero;
final _clock = Clock(() => now.add(elapsed));
await withClock(_clock, () async {
print(DateFormat.yMd().format(clock.now())); // 1/1/2000
elapsed = const Duration(days: 2);
print(DateFormat.yMd().format(clock.now())); // 1/3/2000
});
fake_asyncでは時間を経過させると、Timerの処理も進めるようになっています。状況に応じて使い分けたいと思います。
test('Timer', () {
FakeAsync().run((fake) {
Timer(const Duration(seconds: 30), expectAsync0(() {}));
fake.elapse(const Duration(seconds: 30));
});
});