Test Utilities

Version 1.0.0 Updated Aug 24, 2026

Test helpers are available behind the test-util feature. They are not hidden behind #[cfg(test)], so downstream crates can enable them in their own dev-dependencies.

[dev-dependencies]
monotony = { version = "1.0.0", features = ["test-util"] }

Use FixedMonotonicClock::with_elapsed(...) for code that calls now() exactly twice. Use QueuedMonotonicClock::from_instants(...) when a test needs several pre-seeded instants. Use ManualMonotonicClock::advance(...) for polling loops and timeout code where the test should explicitly move time between observations. Use SharedManualMonotonicClock when code under test must own one clock handle while the test advances time through another cloned handle.

Fixed elapsed time

Use FixedMonotonicClock when the code under test reads the clock once before work starts and once after it finishes.

use std::time::Duration;

use monotony::{MonotonicClock, test_util::FixedMonotonicClock};

fn measure_elapsed(clock: &dyn MonotonicClock) -> Duration {
    let started_at = clock.now();
    clock.now().duration_since(started_at)
}

let clock = FixedMonotonicClock::with_elapsed(Duration::from_millis(250));

assert_eq!(measure_elapsed(&clock), Duration::from_millis(250));

Queued instants

Use QueuedMonotonicClock when the code under test needs a sequence of known clock readings.

use std::time::{Duration, Instant};

use monotony::{MonotonicClock, test_util::QueuedMonotonicClock};

fn collect_ticks(clock: &dyn MonotonicClock, count: usize) -> Vec<Instant> {
    (0..count).map(|_| clock.now()).collect()
}

let first = Instant::now();
let second = first + Duration::from_millis(10);
let third = second + Duration::from_millis(20);
let clock = QueuedMonotonicClock::from_instants([first, second, third]);

assert_eq!(collect_ticks(&clock, 3), vec![first, second, third]);

Manual time

Use ManualMonotonicClock when the test needs to move time between repeated observations, such as timeout or polling code.

use std::time::{Duration, Instant};

use monotony::{MonotonicClock, test_util::ManualMonotonicClock};

fn has_timed_out(clock: &dyn MonotonicClock, started_at: Instant) -> bool {
    clock.now().duration_since(started_at) >= Duration::from_secs(5)
}

let started_at = Instant::now();
let clock = ManualMonotonicClock::new(started_at);

assert!(!has_timed_out(&clock, started_at));

clock.advance(Duration::from_secs(5));

assert!(has_timed_out(&clock, started_at));

Shared manual time

Use SharedManualMonotonicClock when one component observes time through an owned clock handle and the test needs a separate handle for advancing time.

use std::time::{Duration, Instant};

use monotony::{MonotonicClock, test_util::SharedManualMonotonicClock};

fn has_timed_out(clock: &dyn MonotonicClock, started_at: Instant) -> bool {
    clock.now().duration_since(started_at) >= Duration::from_secs(5)
}

let started_at = Instant::now();
let observed_clock = SharedManualMonotonicClock::new(started_at);
let controller = observed_clock.clone();

assert!(!has_timed_out(&observed_clock, started_at));

controller.advance(Duration::from_secs(5));

assert!(has_timed_out(&observed_clock, started_at));