All posts

Testing Error Scenarios in Rust Applications

Write robust Rust error scenario tests with Result types, custom error enums, mock traits, and property-based testing for edge cases.

Testing Error Scenarios in Rust Applications

Rust's type system catches many errors at compile time, but runtime error scenarios still need testing. Here's how to test failure paths effectively.

Test Result Types

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_config_missing_field_returns_error() {
        let input = r#"{"port": 8080}"#; // missing "host"
        let result = parse_config(input);

        assert!(result.is_err());
        assert_eq!(
            result.unwrap_err().to_string(),
            "missing field: host"
        );
    }

    #[test]
    fn divide_by_zero_returns_error() {
        let result = safe_divide(10.0, 0.0);
        assert!(matches!(result, Err(MathError::DivisionByZero)));
    }
}

Custom Error Enums

#[derive(Debug, thiserror::Error)]
enum AppError {
    #[error("not found: {0}")]
    NotFound(String),
    #[error("database error: {0}")]
    Database(#[from] sqlx::Error),
    #[error("validation error: {0}")]
    Validation(String),
}

#[test]
fn get_user_not_found() {
    let repo = MockUserRepo::new();
    let result = get_user(&repo, "nonexistent");

    match result {
        Err(AppError::NotFound(msg)) => assert!(msg.contains("nonexistent")),
        other => panic!("Expected NotFound, got: {:?}", other),
    }
}

Mock Traits for Failure Injection

trait Database {
    fn query(&self, sql: &str) -> Result<Vec<Row>, DbError>;
}

struct FailingDatabase;

impl Database for FailingDatabase {
    fn query(&self, _sql: &str) -> Result<Vec<Row>, DbError> {
        Err(DbError::ConnectionRefused)
    }
}

#[test]
fn service_handles_database_failure() {
    let db = FailingDatabase;
    let service = UserService::new(Box::new(db));

    let result = service.list_users();
    assert!(matches!(result, Err(AppError::Database(_))));
}

Test Panics

#[test]
#[should_panic(expected = "index out of bounds")]
fn access_invalid_index_panics() {
    let items = vec![1, 2, 3];
    let _ = items[10];
}

Async Error Testing

#[tokio::test]
async fn fetch_timeout_returns_error() {
    let client = reqwest::Client::builder()
        .timeout(Duration::from_millis(1))
        .build()
        .unwrap();

    let result = client.get("http://httpbin.org/delay/10").send().await;
    assert!(result.is_err());
}

For production Rust services, integrate Bugsly to capture panics and unexpected errors. Rust's safety guarantees handle memory issues, but logic errors and external failures still need monitoring.

Try Bugsly Free

AI-powered error tracking that explains your bugs. Set up in 2 minutes, free forever for small projects.

Get Started Free