Use the tokio runtime with the #[tokio::test] attribute to run async tests, as standard #[test] attributes cannot handle async functions directly. For more complex scenarios involving time manipulation or multiple runtimes, leverage tokio::time::pause() to control virtual time or use tokio::test with specific runtime flavors.
Here is a basic example using tokio to test an async function that simulates a delay:
use tokio::time::{sleep, Duration};
async fn fetch_data() -> String {
sleep(Duration::from_millis(100)).await;
"data".to_string()
}
#[tokio::test]
async fn test_fetch_data() {
let result = fetch_data().await;
assert_eq!(result, "data");
}
If your async logic depends on real-time delays, testing can be slow and flaky. You can use tokio::test with a paused runtime to make time-based tests instant and deterministic:
use tokio::time::{sleep, Duration};
async fn delayed_task() -> bool {
sleep(Duration::from_secs(1)).await;
true
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_delayed_task_with_paused_time() {
// This test runs instantly because time is paused
let result = delayed_task().await;
assert!(result);
}
For testing code that uses async-std instead of tokio, use the #[async_std::test] attribute from the async-std crate. Ensure your Cargo.toml includes the necessary dev-dependencies:
[dev-dependencies]
tokio = { version = "1", features = ["full"] }
# OR for async-std
# async-std = { version = "1", features = ["attributes"] }
When testing async functions that return Result, remember to unwrap or handle the error explicitly within the test, just like in synchronous code. If you need to test concurrency patterns, such as multiple tasks running simultaneously, spawn them using tokio::spawn and join them with join! or join_all to verify their collective behavior.
use tokio::task::JoinHandle;
async fn worker(id: u32) -> u32 {
id * 2
}
#[tokio::test]
async fn test_concurrent_workers() {
let h1 = tokio::spawn(worker(1));
let h2 = tokio::spawn(worker(2));
let (r1, r2) = tokio::join!(h1, h2);
assert_eq!(r1.unwrap(), 2);
assert_eq!(r2.unwrap(), 4);
}
Always ensure your test environment matches the runtime configuration of your production code, especially regarding thread counts and runtime flavors (current_thread vs. multi_thread), to catch potential deadlocks or race conditions early.