TestClock
GPT-4.icon
「テスト中に時間を制御できる機能」
これにより、実際に待つことなく Effect.sleep やタイムアウト処理など、時間に依存するコードのテストが効率的に行えるようになります。
---
🔧 基本:TestClock とは?
通常の実行では Effect.sleep("1 minute") のように書いた場合、本当に1分間待つ必要があります。しかし、テスト中にそんな時間はかけられませんよね。
TestClock は、仮想の時計(テスト専用の時間) を使って、以下のようなことを可能にします:
TestClock.adjust("1 minute") → 1分進める
TestClock.setTime(...) → 指定時刻にジャンプ
実際には経っていない時間をシミュレートし、タイムアウトや再試行などの動作を即座に検証できる
Vitestに似たようなのあったなmrsekut.icon
🔁 よくあるユースケースと例
① タイムアウトのテスト
code:ts
const fiber = yield* Effect.sleep("5 minutes").pipe(
Effect.timeoutTo({
duration: "1 minute",
onSuccess: Option.some,
onTimeout: () => Option.none<void>()
}),
Effect.fork
)
yield* TestClock.adjust("1 minute")
const result = yield* Fiber.join(fiber)
assert.ok(Option.isNone(result)) // タイムアウトしてるはず
💡 ポイント:
adjust によって時間を進めると、待機していた処理が動く
実際の時間は経っていない
---
② 定期実行のテスト
code:ts
yield* Queue.offer(q, undefined).pipe(
Effect.delay("60 minutes"),
Effect.forever,
Effect.fork
)
yield* TestClock.adjust("60 minutes")
const result = yield* Queue.take(q)
💡 ポイント:
時計を60分進めると1つのタスクが実行され、Queue に入る
さらに60分進めるともう1つ入る
---
code:ts
const startTime = yield* Clock.currentTimeMillis
yield* TestClock.adjust("1 minute")
const endTime = yield* Clock.currentTimeMillis
assert.ok(endTime - startTime >= 60_000)
💡 Clock は「実時間」ではなく、TestClock に従って動作する
---
④ Deferred の組み合わせ
code:ts
const deferred = yield* Deferred.make<number, void>()
yield* Effect.all(
{ concurrency: "unbounded" }
).pipe(Effect.fork)
yield* TestClock.adjust("10 seconds")
const value = yield* Deferred.await(deferred)
assert.ok(value === 1)
💡 Deferred は Promise に似ていて、他のEffectが完了したタイミングで値がセットされる
→ それを sleep と組み合わせて、時間経過後に値が設定される挙動をテスト
---
✅ Best Practice(まとめ)
1. Effect.sleep などをテストするときは fork して非同期実行
2. TestClock を adjust して時間を進める
3. Fiber や Deferred を使ってその結果を検証
4. 実行ごとに副作用が起きるようなロジックでは、進める時間に注意(例:60分ごとに処理が走るときに60分ずつ進める)