Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TestClock — Deterministic Time in Tests

TestClock is a manual Clock implementation. It is useful with retry_with_clock, repeat_with_clock, or code that accepts a Clock explicitly.

The Problem with Real Time in Tests

let eff = retry(
    || failing_call(),
    Schedule::exponential(Duration::from_secs(1)).compose(Schedule::recurs(3)),
);

With the default live clock, this waits in real time. Use TestClock to avoid real sleeps.

TestClock API

use std::time::{Duration, Instant};
use effectful::{Clock, TestClock};

let start = Instant::now();
let clock = TestClock::new(start);

let now: Instant = clock.now();
clock.advance(Duration::from_millis(500));
clock.set_time(start + Duration::from_secs(60));

let pending: Vec<Instant> = clock.pending_sleeps();

pending_sleeps() returns registered sleep deadlines. This is useful for asserting that code scheduled a delay.

run_test_with_clock

run_test_with_clock(effect, env, clock) runs an already-built effect and returns Exit<A, E>. It does not create a closure-based test harness.

use std::time::Instant;
use effectful::{TestClock, run_test_with_clock, succeed};

let clock = TestClock::new(Instant::now());
let exit = run_test_with_clock(succeed::<_, (), ()>(42), (), clock);
assert_eq!(exit, Exit::succeed(42));

Testing Scheduled Work

use std::sync::{Arc, atomic::{AtomicU32, Ordering}};
use std::time::{Duration, Instant};
use effectful::{Schedule, TestClock, repeat_with_clock, run_blocking, succeed};

let clock = TestClock::new(Instant::now());
let counter = Arc::new(AtomicU32::new(0));

let effect = repeat_with_clock(
    {
        let counter = counter.clone();
        move || {
            counter.fetch_add(1, Ordering::Relaxed);
            succeed::<u32, (), ()>(counter.load(Ordering::Relaxed))
        }
    },
    Schedule::spaced(Duration::from_secs(60)).compose(Schedule::recurs(3)),
    clock.clone(),
    None,
);

let value = run_blocking(effect, ())?;
assert_eq!(value, 4); // initial run + 3 repeats

TestClock::sleep records sleeps instead of blocking, so scheduled tests complete quickly.

Fake Services

When business logic needs time, pass a Clock as part of your service/environment design. In production provide LiveClock; in tests provide TestClock.