testkit-async

Crates.iotestkit-async
lib.rstestkit-async
version0.0.1
created_at2025-11-18 22:35:14.565961+00
updated_at2025-11-18 22:35:14.565961+00
descriptionPractical testing tools for async Rust - time control, deterministic execution, and failure injection
homepagehttps://github.com/ibrahimcesar/testkit-async
repositoryhttps://github.com/ibrahimcesar/testkit-async
max_upload_size
id1939091
size45,460
Ibrahim Cesar (ibrahimcesar)

documentation

https://docs.rs/testkit-async

README

testkit-async 🧰

Practical testing tools for async Rust

testkit-async is a comprehensive testing toolkit for async Rust code. It provides time control, deterministic execution, failure injection, and rich assertions to make async testing fast, reliable, and easy.

🎯 Why testkit-async?

The Problem

Testing async code in Rust is frustrating:

#[tokio::test]
async fn test_retry_with_timeout() {
    // This test takes 30+ seconds to run! 😱
    let result = retry_with_timeout(
        failing_operation,
        Duration::from_secs(30)
    ).await;
    
    // How do I know retry happened 3 times?
    // How do I test timeout without waiting 30s?
    // How do I make this deterministic?
}

Common issues:

  • ❌ Tests are slow (waiting for real time)
  • ❌ Tests are flaky (race conditions, timing issues)
  • ❌ Tests are hard to write (complex async coordination)
  • ❌ Tests are unpredictable (non-deterministic execution)

The Solution

use testkit_async::prelude::*;

#[testkit_async::test]
async fn test_retry_with_timeout() {
    let clock = MockClock::new();
    let counter = AtomicU32::new(0);
    
    // Test runs instantly! ⚡
    let future = retry_with_timeout(
        || async { 
            counter.fetch_add(1, Ordering::SeqCst);
            Err("fail") 
        },
        Duration::from_secs(30)
    );
    
    // Advance virtual time - no real waiting!
    clock.advance(Duration::from_secs(31));
    
    // Verify behavior
    assert!(future.await.is_err());
    assert_eq!(counter.load(Ordering::SeqCst), 3); // Retried 3 times!
}

✨ Features (Planned)

  • ⏱️ Mock Clock - Control time without waiting
  • 🎮 Deterministic Executor - Control task execution order
  • 💥 Failure Injection - Simulate errors, timeouts, network issues
  • 🔍 Async Assertions - Fluent API for testing streams and futures
  • 🎯 Sync Points - Coordinate multiple tasks precisely
  • 📊 Test Utilities - Mocks, spies, and test helpers

🚧 Status

Work in Progress - Early development

Current version: 0.1.0-alpha

🆚 Comparison with Existing Tools

What Already Exists

Tool What It Does What's Missing
async-test Attribute macro for async tests ❌ No time control
❌ No execution control
❌ Just a macro wrapper
tokio-test Tokio testing utilities ⚠️ Tokio-specific only
❌ Limited time control
❌ No failure injection
futures-test Futures test utilities ❌ No mock clock
❌ Low-level only
❌ Not ergonomic
mockall General mocking ❌ Not async-aware
❌ Verbose for async

What testkit-async Provides

Feature testkit-async tokio-test futures-test async-test
Mock Clock ✅ Full control ⚠️ Limited
Deterministic Execution
Failure Injection
Async Assertions
Sync Points
Runtime Agnostic ❌ Tokio only
Ergonomic API ⚠️ ⚠️

Key Differentiators:

  1. Complete Time Control - Not just pause/resume, but full virtual time
  2. Deterministic Testing - Control exact execution order of tasks
  3. Chaos Engineering - Built-in failure injection and network simulation
  4. High-Level API - Ergonomic, not low-level primitives

📚 Quick Examples (Planned API)

Time Control

use testkit_async::prelude::*;

#[testkit_async::test]
async fn test_with_timeout() {
    let clock = MockClock::new();
    
    // This completes instantly in tests!
    let future = timeout(Duration::from_secs(30), slow_operation());
    
    // Advance virtual time
    clock.advance(Duration::from_secs(31));
    
    // Timeout triggered without waiting 30s
    assert!(future.await.is_err());
}

Controlled Concurrency

use testkit_async::prelude::*;

#[testkit_async::test]
async fn test_race_condition() {
    let executor = TestExecutor::new();
    let counter = Arc::new(Mutex::new(0));
    
    // Spawn two tasks
    let c1 = counter.clone();
    executor.spawn(async move {
        sync_point("before").await;
        *c1.lock().await += 1;
    });
    
    let c2 = counter.clone();
    executor.spawn(async move {
        sync_point("before").await;
        *c2.lock().await += 1;
    });
    
    // Release both simultaneously - guaranteed race!
    executor.release("before");
    executor.run_until_idle().await;
    
    // Now you can test race condition handling
}

Failure Injection

use testkit_async::chaos::FailureInjector;

#[testkit_async::test]
async fn test_retry_logic() {
    let injector = FailureInjector::new()
        .fail_first(3)  // First 3 calls fail
        .then_succeed();
    
    let client = HttpClient::new()
        .with_interceptor(injector);
    
    let result = retry_request(&client).await?;
    
    // Verify retry worked
    assert_eq!(injector.attempt_count(), 4); // 3 failures + 1 success
    assert!(result.is_ok());
}

Async Assertions

use testkit_async::prelude::*;

#[testkit_async::test]
async fn test_stream() {
    let stream = create_data_stream();
    
    // Fluent assertions for streams
    assert_stream!(stream)
        .next_eq(1).await
        .next_eq(2).await
        .next_eq(3).await
        .ends().await;
    
    // Timing assertions
    assert_completes_within!(
        Duration::from_millis(100),
        fast_operation()
    ).await;
}

Mock Async Dependencies

use testkit_async::mock::*;

#[async_trait]
trait DataStore {
    async fn fetch(&self, id: u64) -> Result<Data>;
}

#[testkit_async::test]
async fn test_with_mock() {
    let mut mock = MockDataStore::new();
    
    // Setup expectations
    mock.expect_fetch()
        .with(eq(42))
        .times(1)
        .returning(|_| Ok(Data { value: 100 }));
    
    // Use the mock
    let result = process_data(&mock, 42).await?;
    
    // Verify
    assert_eq!(result.value, 100);
    mock.verify();
}

🎯 Use Cases

Fast Test Suites

// Before: Test suite takes 5 minutes (lots of sleeps/timeouts)
// After: Test suite takes 5 seconds (virtual time)

Deterministic Tests

// Before: Flaky tests due to race conditions
// After: Deterministic execution, reproducible failures

Chaos Engineering

// Test resilience to:
// - Network timeouts
// - Random failures
// - Slow responses
// - Connection drops

Integration Testing

// Test complex async interactions:
// - Multiple services communicating
// - Event-driven systems
// - Stream processing pipelines

📦 Installation

# Not yet published - coming soon!
cargo add --dev testkit-async

# Or in Cargo.toml:
[dev-dependencies]
testkit-async = "0.1"

🗺️ Roadmap

Phase 1: Time Control (Current)

  • Mock clock implementation
  • Time advancement APIs
  • Integration with tokio::time
  • Pause/resume time

Phase 2: Execution Control

  • Test executor
  • Sync points
  • Step-by-step execution
  • Task inspection

Phase 3: Chaos Engineering

  • Failure injector
  • Network simulator
  • Latency injection
  • Resource exhaustion simulation

Phase 4: Assertions & Utilities

  • Async assertion macros
  • Stream testing helpers
  • Mock trait generation
  • Snapshot testing for async

Phase 5: Ecosystem Integration

  • Tokio integration
  • async-std integration
  • smol integration
  • Runtime-agnostic core

🎨 Design Philosophy

Ergonomics First:

  • Simple for common cases
  • Powerful for complex scenarios
  • Minimal boilerplate

Determinism:

  • Reproducible test results
  • No timing-dependent failures
  • Controlled execution order

Fast:

  • Tests run at CPU speed, not wall-clock time
  • Parallel-friendly
  • Efficient mocking

Composable:

  • Mix and match features
  • Works with existing tools
  • Not all-or-nothing

🤝 Contributing

Contributions welcome! This project is in early stages.

Priority areas:

  • Mock clock implementation
  • Test executor design
  • Failure injection patterns
  • Documentation and examples
  • Runtime compatibility

📝 License

MIT OR Apache-2.0

🙏 Acknowledgments

Inspired by:

🔗 Related Projects


testkit-async - Making async testing practical 🧰

Status: 🚧 Pre-alpha - Core architecture in design

Star ⭐ this repo to follow development!

Commit count: 0

cargo fmt