Rust Async Testing Patterns
Best practices for testing async Rust code with Tokio.
Core Patterns
Basic Async Test
#[tokio::test] async fn test_episode_creation() { let memory = SelfLearningMemory::new(Default::default()).await?; let id = memory.start_episode("Test", ctx, TaskType::CodeGen).await; assert!(!id.is_empty()); }
Time-Based Testing
#[tokio::test(start_paused = true)] async fn test_timeout_behavior() { // Time advances only when awaited let start = tokio::time::Instant::now(); tokio::time::sleep(Duration::from_secs(5)).await; assert!(start.elapsed().as_millis() < 100); }
Concurrent Operations
#[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_concurrent_episodes() { let memory = Arc::new(setup_memory().await);
let handles: Vec<_> = (0..10).map(|i| {
let mem = memory.clone();
tokio::spawn(async move {
mem.start_episode(format!("Task {}", i), ctx, type_).await
})
}).collect();
let results = futures::future::join_all(handles).await;
assert_eq!(results.len(), 10);
}
Timeout Testing
#[tokio::test] async fn test_operation_timeout() { let result = tokio::time::timeout( Duration::from_secs(2), slow_operation() ).await; assert!(result.is_err()); }
Best Practices
-
Use #[tokio::test] instead of block_on
-
Enable start_paused = true for time tests
-
Use multi_thread for concurrency tests
-
Mock external dependencies
-
Test error paths
Common Pitfalls
Bad Good
std::thread::sleep()
tokio::time::sleep().await
memory.start_episode()
memory.start_episode().await
Single-threaded for concurrency multi_thread runtime
Memory-Specific Pattern
#[tokio::test] async fn test_complete_lifecycle() { let memory = setup_memory().await;
// Start → Log Steps → Complete → Verify
let id = memory.start_episode("test", ctx, type_).await;
memory.log_execution_step(id.clone(), step).await;
memory.complete_episode(id.clone(), outcome, None).await?;
let episode = memory.get_episode(&id).await?;
assert_eq!(episode.outcome, outcome);
}