๐ŸŽ“ A1Learner
๐Ÿ”

Rust Tokio Testing

`#[tokio::test]` is an attribute that runs an async test on a fresh runtime. Tokio timers in a test can run on a paused clock.

#[tokio::test] makes a test async

The attribute turns an async test fn into a normal test: it builds a runtime and block_on's the body:

#[tokio::test]
async fn it_adds() {
    assert_eq!(2 + 2, 4);
}

Need the multi-thread shape?

Ask for it in the attribute, with the same flavour spelling as the runtime Builder:

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn it_spawns() {
    let handle = tokio::spawn(async { 40 + 2 });
    assert_eq!(handle.await.unwrap(), 42);
}

pause() freezes the clock

tokio::time::pause() freezes the async clock. A sleep of a whole hour then completes almost instantly, once no other work is pending. And it still measures the full hour:

#[tokio::test]
async fn an_hour_passes_fast() {
    tokio::time::pause();
    let start = Instant::now();
    sleep(Duration::from_secs(3600)).await;
    assert!(start.elapsed() >= Duration::from_secs(3600));
}

advance() steps the clock by hand

advance(duration) moves a paused clock forward by hand, step by step:

#[tokio::test]
async fn step_the_clock() {
    tokio::time::pause();
    let start = Instant::now();
    tokio::time::advance(Duration::from_secs(30)).await;
    assert_eq!(start.elapsed(), Duration::from_secs(30));
}

Example

// Cargo.toml: tokio = { version = "1", features = ["full", "test-util"] }
use tokio::time::{sleep, Duration, Instant};

#[tokio::main(flavor = "current_thread")]
async fn main() {
    // In a real project, this body sits in a #[tokio::test] fn instead of main.
    tokio::time::pause();
    let start = Instant::now();
    sleep(Duration::from_secs(3600)).await;
    println!("slept for {:?}", start.elapsed());
}

Key points

  • Each test gets its own current-thread runtime by default, so tests stay independent
  • Pausing affects only Tokio's timers.

In this example, pause() freezes Tokio's clock before the sleep begins. The sleep asks for a whole hour. But with nothing else to do, the runtime jumps the clock straight to the wake-up time. The printed elapsed time is the full hour, yet the program finishes in a blink. In a test, #[tokio::test] would replace #[tokio::main] on the function.

Note

std::time and real I/O still live in the real world.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Rust Playground

Exercise

What does tokio::time::pause() do in a test?