Rust AWS SDK模拟测试库rusoto_mock的使用,rusoto_mock助力Rust开发者高效进行AWS服务单元测试和集成测试

Rust AWS SDK模拟测试库rusoto_mock的使用,rusoto_mock助力Rust开发者高效进行AWS服务单元测试和集成测试

安装

在项目目录中运行以下Cargo命令:

cargo add rusoto_mock

或者在Cargo.toml中添加以下行:

rusoto_mock = "0.48.0"

使用示例

rusoto_mock是一个用于Rust开发者进行AWS服务单元测试和集成测试的模拟测试库。它可以帮助开发者在不实际调用AWS服务的情况下进行测试。

以下是一个完整的示例demo,展示如何使用rusoto_mock进行测试:

#[cfg(test)]
mod tests {
    use rusoto_core::Region;
    use rusoto_s3::{S3, S3Client, ListBucketsRequest};
    use rusoto_mock::{MockRequestDispatcher, MockResponse};
    use std::collections::HashMap;

    #[test]
    fn test_list_buckets() {
        // 创建模拟响应
        let mock_response = MockResponse::new(200)
            .with_body(r#"{"Buckets":[{"Name":"test-bucket","CreationDate":"2022-01-01T00:00:00.000Z"}],"Owner":{"DisplayName":"test-owner","ID":"test-id"}}"#)
            .with_header("x-amz-request-id", "test-request-id");

        // 设置模拟调度器
        let dispatcher = MockRequestDispatcher::with_status(200)
            .with_response(mock_response)
            .with_header("content-type", "application/xml");

        // 创建模拟的S3客户端
        let client = S3Client::new_with(dispatcher, Region::UsEast1);

        // 执行测试
        let result = client.list_buckets(ListBucketsRequest::default()).sync();

        // 验证结果
        assert!(result.is_ok());
        let response = result.unwrap();
        assert_eq!(response.buckets.unwrap()[0].name.as_ref().unwrap(), "test-bucket");
    }
}

示例说明

  1. 创建模拟响应:使用MockResponse构建一个模拟的AWS API响应,可以设置状态码、响应体和头部信息。

  2. 设置模拟调度器MockRequestDispatcher用于处理模拟请求,可以配置默认状态码、响应和头部。

  3. 创建模拟客户端:使用new_with方法创建带有模拟调度器的AWS服务客户端。

  4. 执行测试和验证:像使用真实客户端一样调用AWS服务API,然后验证返回结果是否符合预期。

主要特点

  • 无需实际调用AWS服务即可进行测试
  • 支持自定义响应状态码、头部和内容
  • 可以与各种AWS服务客户端配合使用
  • 适用于单元测试和集成测试场景

完整示例代码

以下是另一个使用rusoto_mock测试DynamoDB服务的完整示例:

#[cfg(test)]
mod dynamodb_tests {
    use rusoto_core::Region;
    use rusoto_dynamodb::{
        DynamoDb, DynamoDbClient, 
        GetItemInput, AttributeValue,
        GetItemError, GetItemOutput
    };
    use rusoto_mock::{MockRequestDispatcher, MockResponse};
    use std::collections::HashMap;

    #[test]
    fn test_get_item() {
        // 准备模拟响应数据
        let mut item = HashMap::new();
        item.insert(
            "id".to_string(),
            AttributeValue {
                s: Some("123".to_string()),
                ..Default::default()
            }
        );
        
        let mock_output = GetItemOutput {
            item: Some(item),
            ..Default::default()
        };

        // 创建模拟响应
        let mock_response = MockResponse::new(200)
            .with_body(serde_json::to_string(&mock_output).unwrap())
            .with_header("x-amz-request-id", "dynamo-test-request");

        // 设置模拟调度器
        let dispatcher = MockRequestDispatcher::with_status(200)
            .with_response(mock_response);

        // 创建模拟的DynamoDB客户端
        let client = DynamoDbClient::new_with(dispatcher, Region::UsWest2);

        // 准备查询参数
        let mut key = HashMap::new();
        key.insert(
            "id".to_string(),
            AttributeValue {
                s: Some("123".to_string()),
                ..Default::default()
            }
        );
        
        let request = GetItemInput {
            table_name: "test_table".to_string(),
            key,
            ..Default::default()
        };

        // 执行测试
        let result = client.get_item(request).sync();

        // 验证结果
        assert!(result.is_ok());
        let response = result.unwrap();
        assert_eq!(response.item.unwrap()["id"].s.as_ref().unwrap(), "123");
    }

    #[test]
    fn test_error_response() {
        // 模拟错误响应
        let mock_error = GetItemError::ResourceNotFound("Table not found".to_string());
        
        let mock_response = MockResponse::new(404)
            .with_body(serde_json::to_string(&mock_error).unwrap());

        let dispatcher = MockRequestDispatcher::with_status(404)
            .with_response(mock_response);

        let client = DynamoDbClient::new_with(dispatcher, Region::UsWest2);

        let request = GetItemInput {
            table_name: "non_existent_table".to_string(),
            key: HashMap::new(),
            ..Default::default()
        };

        let result = client.get_item(request).sync();

        // 验证错误响应
        assert!(result.is_err());
    }
}

完整示例说明

  1. DynamoDB模拟测试:展示了如何测试DynamoDB的GetItem操作,包括成功和错误场景

  2. 模拟响应构建:使用serde_json将Rust结构体序列化为JSON响应体

  3. 错误处理测试:演示了如何测试AWS服务返回的错误响应

  4. 复杂数据结构:展示了如何处理DynamoDB特有的AttributeValue类型

通过这个完整示例,开发者可以了解如何:

  • 测试不同的AWS服务
  • 处理复杂的请求和响应结构
  • 模拟错误场景
  • 验证各种返回结果

1 回复

Rust AWS SDK模拟测试库rusoto_mock使用指南

介绍

rusoto_mock是一个用于Rust开发的AWS SDK模拟测试库,它允许开发者在本地环境中模拟AWS服务的行为,从而高效地进行单元测试和集成测试,而无需连接实际的AWS服务或支付云服务费用。

主要特点

  • 完全模拟AWS服务API响应
  • 无需网络连接即可测试AWS相关代码
  • 可配置的模拟响应
  • 支持大多数rusoto服务
  • 简化CI/CD流程中的测试环节

安装方法

在Cargo.toml中添加依赖:

[dependencies]
rusoto_core = "0.47.0"
rusoto_s3 = "0.47.0"
rusoto_mock = "0.47.0"

基本使用方法

1. 创建模拟客户端

use rusoto_core::{Region, HttpClient};
use rusoto_mock::{MockRequestDispatcher, MockResponse};
use rusoto_s3::{S3Client, ListBucketsRequest};

// 创建模拟响应
let mock_response = MockResponse::new(200)
    .with_body(r#"{"Buckets": [{"Name": "test-bucket"}], "Owner": {"DisplayName": "test"}}"#);

// 创建模拟客户端
let mock_dispatcher = MockRequestDispatcher::with_status(200)
    .with_body(mock_response);
let s3_client = S3Client::new_with(mock_dispatcher, HttpClient::new().unwrap(), Region::UsWest2);

2. 测试S3列表桶操作

#[tokio::test]
async fn test_list_buckets() {
    let mock_response = MockResponse::new(200)
        .with_body(r#"{"Buckets": [{"Name": "test-bucket-1"}, {"Name": "test-bucket-2"}], "Owner": {"DisplayName": "test"}}"#);
    
    let mock_dispatcher = MockRequestDispatcher::with_status(200)
        .with_body(mock_response);
    
    let s3_client = S3Client::new_with(mock_dispatcher, HttpClient::new().unwrap(), Region::UsWest2);
    
    let result = s3_client.list_buckets(ListBucketsRequest::default()).await;
    assert!(result.is_ok());
    
    let response = result.unwrap();
    assert_eq(response.buckets.unwrap().len(), 2);
}

3. 模拟错误响应

#[tokio::test]
async fn test_s3_error_response() {
    let mock_response = MockResponse::new(403)
        .with_body(r#"{"Code": "AccessDenied", "Message": "Access Denied"}"#);
    
    let mock_dispatcher = MockRequestDispatcher::with_status(403)
        .with_body(mock_response);
    
    let s3_client = S3Client::new_with(mock_dispatcher, HttpClient::new().unwrap(), Region::UsWest2);
    
    let result = s3_client.list_buckets(ListBucketsRequest::default()).await;
    assert!(result.is_err());
    
    if let Err(e) = result {
        assert_eq!(e.to_string(), "AccessDenied: Access Denied");
    }
}

高级用法

1. 基于请求内容返回不同响应

use rusoto_s3::{GetObjectRequest, PutObjectRequest};

let mock_dispatcher = MockRequestDispatcher::default()
    .with_request_checker(|req| {
        if req.path.contains("test-key") {
            MockResponse::new(200).with_body("test content")
        } else {
            MockResponse::new(404).with_body(r#"{"Code": "NoSuchKey", "Message": "The specified key does not exist."}"#)
        }
    });

let s3_client = S3Client::new_with(mock_dispatcher, HttpClient::new().unwrap(), Region::UsWest2);

2. 模拟DynamoDB操作

use rusoto_dynamodb::{DynamoDbClient, GetItemInput, AttributeValue};

#[tokio::test]
async fn test_dynamodb_get_item() {
    let mock_response = MockResponse::new(200)
        .with_body(r#"{
            "Item": {
                "id": {"S": "123"},
                "name": {"S": "test-item"}
            }
        }"#);
    
    let mock_dispatcher = MockRequestDispatcher::with_status(200)
        .with_body(mock_response);
    
    let dynamodb_client = DynamoDbClient::new_with(mock_dispatcher, HttpClient::new().unwrap(), Region::UsWest2);
    
    let mut request = GetItemInput::default();
    request.table_name = "test-table".to_string();
    request.key = {
        let mut key = std::collections::HashMap::new();
        key.insert("id".to_string(), AttributeValue {
            s: Some("123".to_string()),
            ..Default::default()
        });
        key
    };
    
    let result = dynamodb_client.get_item(request).await;
    assert!(result.is_ok());
    
    let response = result.unwrap();
    let item = response.item.unwrap();
    assert_eq!(item.get("name").unwrap().s.as_ref().unwrap(), "test-item");
}

完整示例

下面是一个完整的S3模拟测试示例,包含多个测试场景:

use rusoto_core::{Region, HttpClient};
use rusoto_mock::{MockRequestDispatcher, MockResponse};
use rusoto_s3::{S3Client, ListBucketsRequest, PutObjectRequest, GetObjectRequest};

mod tests {
    use super::*;
    
    // 测试S3列表桶操作
    #[tokio::test]
    async fn test_list_buckets() {
        // 准备模拟响应数据
        let mock_response = MockResponse::new(200)
            .with_body(r#"{"Buckets": [{"Name": "test-bucket-1"}, {"Name": "test-bucket-2"}], "Owner": {"DisplayName": "test"}}"#);
        
        // 创建模拟分发器
        let mock_dispatcher = MockRequestDispatcher::with_status(200)
            .with_body(mock_response);
        
        // 创建S3模拟客户端
        let s3_client = S3Client::new_with(
            mock_dispatcher, 
            HttpClient::new().unwrap(), 
            Region::UsWest2
        );
        
        // 执行列表桶操作
        let result = s3_client.list_buckets(ListBucketsRequest::default()).await;
        
        // 验证结果
        assert!(result.is_ok());
        let response = result.unwrap();
        assert_eq!(response.buckets.unwrap().len(), 2);
    }
    
    // 测试S3上传对象
    #[tokio::test]
    async fn test_put_object() {
        // 准备模拟成功响应
        let mock_response = MockResponse::new(200)
            .with_body(r#"{"ETag": "\"d41d8cd98f00b204e9800998ecf8427e\""}"#);
        
        let mock_dispatcher = MockRequestDispatcher::with_status(200)
            .with_body(mock_response);
        
        let s3_client = S3Client::new_with(
            mock_dispatcher, 
            HttpClient::new().unwrap(), 
            Region::UsWest2
        );
        
        // 创建上传对象请求
        let request = PutObjectRequest {
            bucket: "test-bucket".to_string(),
            key: "test-key".to_string(),
            body: Some("test content".as_bytes().to_vec().into()),
            ..Default::default()
        };
        
        // 执行上传操作
        let result = s3_client.put_object(request).await;
        
        // 验证结果
        assert!(result.is_ok());
        let response = result.unwrap();
        assert_eq!(response.e_tag, Some("\"d41d8cd98f00b204e9800998ecf8427e\"".to_string()));
    }
    
    // 测试S3获取对象
    #[tokio::test]
    async fn test_get_object() {
        // 模拟对象内容响应
        let mock_response = MockResponse::new(200)
            .with_body("test object content");
        
        let mock_dispatcher = MockRequestDispatcher::with_status(200)
            .with_body(mock_response);
        
        let s3_client = S3Client::new_with(
            mock_dispatcher, 
            HttpClient::new().unwrap(), 
            Region::UsWest2
        );
        
        // 创建获取对象请求
        let request = GetObjectRequest {
            bucket: "test-bucket".to_string(),
            key: "test-key".to_string(),
            ..Default::default()
        };
        
        // 执行获取操作
        let result = s3_client.get_object(request).await;
        
        // 验证结果
        assert!(result.is_ok());
        let response = result.unwrap();
        let bytes = response.body.unwrap().into_blocking_read();
        let mut content = String::new();
        bytes.read_to_string(&mut content).unwrap();
        assert_eq!(content, "test object content");
    }
    
    // 测试S3操作的错误场景
    #[tokio::test]
    async fn test_s3_error_scenarios() {
        // 模拟访问被拒绝的错误响应
        let mock_response = MockResponse::new(403)
            .with_body(r#"{"Code": "AccessDenied", "Message": "Access Denied"}"#);
        
        let mock_dispatcher = MockRequestDispatcher::with_status(403)
            .with_body(mock_response);
        
        let s3_client = S3Client::new_with(
            mock_dispatcher, 
            HttpClient::new().unwrap(), 
            Region::UsWest2
        );
        
        // 执行列表桶操作(预期会失败)
        let result = s3_client.list_buckets(ListBucketsRequest::default()).await;
        
        // 验证错误
        assert!(result.is_err());
        if let Err(e) = result {
            assert_eq!(e.to_string(), "AccessDenied: Access Denied");
        }
    }
}

最佳实践

  1. 组织测试代码:为不同的服务创建单独的测试模块
  2. 重用模拟客户端:考虑在测试模块中创建工厂函数来生成模拟客户端
  3. 验证请求:确保你的测试不仅验证响应,还验证发送的请求
  4. 覆盖错误场景:测试各种错误响应以确保错误处理逻辑正确
  5. 结合真实测试:在CI中同时运行模拟测试和少量真实AWS集成测试

rusoto_mock是Rust开发者测试AWS相关代码的强大工具,可以显著提高测试效率和可靠性,同时降低测试成本。

回到顶部