Crate aws_smithy_mocks

source ·
Expand description

§aws-smithy-mocks

A flexible mocking framework for testing clients generated by smithy-rs, including all packages of the AWS SDK for Rust.

This crate provides a simple yet powerful way to mock SDK client responses for testing purposes. It uses interceptors to return stub responses, allowing you to test both happy-path and error scenarios without mocking the entire client or using traits.

§Key Features

  • Simple API: Create mock rules with a fluent API using the mock! macro
  • Flexible Response Types: Return modeled outputs, errors, or raw HTTP responses
  • Request Matching: Match requests based on their properties
  • Response Sequencing: Define sequences of responses for testing retry behavior
  • Rule Modes: Control how rules are matched and applied

§Basic Usage

use aws_sdk_s3::operation::get_object::GetObjectOutput;
use aws_sdk_s3::Client;
use aws_smithy_types::byte_stream::ByteStream;
use aws_smithy_mocks::{mock, mock_client};

#[tokio::test]
async fn test_s3_get_object() {
    // Create a rule that returns a successful response
    let get_object_rule = mock!(Client::get_object)
        .match_requests(|req| req.bucket() == Some("test-bucket") && req.key() == Some("test-key"))
        .then_output(|| GetObjectOutput::builder()
            .body(ByteStream::from_static(b"test-content"))
            .build()
         );

    // Create a mocked client with the rule
    let s3 = mock_client!(aws_sdk_s3, [&get_object_rule]);

    // Use the client as you would normally
    let result = s3.get_object()
        .bucket("test-bucket")
        .key("test-key")
        .send()
        .await
        .expect("success response");

    // Verify the response
    let data = result.body.collect().await.expect("successful read").to_vec();
    assert_eq!(data, b"test-content");

    // Verify the rule was used
    assert_eq!(get_object_rule.num_calls(), 1);
}

§Creating Rules

Rules are created using the mock! macro, which takes a client operation as an argument:

let rule = mock!(Client::get_object)
    // Optional: Add a matcher to filter requests
    .match_requests(|req| req.bucket() == Some("test-bucket"))
    // Add a response
    .then_output(|| GetObjectOutput::builder().build());

§Response Types

You can return different types of responses:

// Return a modeled output
let success_rule = mock!(Client::get_object)
    .then_output(|| GetObjectOutput::builder().build());

// Return a modeled error
let error_rule = mock!(Client::get_object)
    .then_error(|| GetObjectError::NoSuchKey(NoSuchKey::builder().build()));

// Return an HTTP response
let http_rule = mock!(Client::get_object)
    .then_http_response(|| HttpResponse::new(
        StatusCode::try_from(503).unwrap(),
        SdkBody::from("service unavailable")
    ));

§Response Sequences

For testing retry behavior or complex scenarios, you can define sequences of responses using the sequence builder API:

let retry_rule = mock!(Client::get_object)
    .sequence()
    .http_status(503, None)                          // First call returns 503
    .http_status(503, None)                          // Second call returns 503
    .output(|| GetObjectOutput::builder().build())   // Third call succeeds
    .build();

// With repetition using `times()`
let retry_rule = mock!(Client::get_object)
    .sequence()
    .http_status(503, None)
    .times(2)                                        // First two calls return 503
    .output(|| GetObjectOutput::builder().build())   // Third call succeeds
    .build();

The sequence builder API provides a fluent interface for defining sequences of responses. After providing all responses in the sequence, the rule is considered exhausted.

§Creating Mocked Clients

Use the mock_client! macro to create a client with your rules:

// Create a client with a single rule
let client = mock_client!(aws_sdk_s3, [&rule]);

// Create a client with multiple rules and a specific rule mode
let client = mock_client!(aws_sdk_s3, RuleMode::Sequential, [&rule1, &rule2]);

// Create a client with additional configuration
let client = mock_client!(
    aws_sdk_s3,
    RuleMode::Sequential,
    [&rule],
    |config| config.force_path_style(true)
);

§Rule Modes

The RuleMode enum controls how rules are matched and applied:

  • RuleMode::Sequential: Rules are tried in order. When a rule is exhausted, the next rule is used.
  • RuleMode::MatchAny: The first matching rule is used, regardless of order.
let interceptor = MockResponseInterceptor::new()
    .rule_mode(RuleMode::Sequential)
    .with_rule(&rule1)
    .with_rule(&rule2);

§Testing Retry Behavior

The mocking framework supports testing retry behavior by allowing you to define sequences of responses:

#[tokio::test]
async fn test_retry() {
    // Create a rule that returns errors for the first two attempts, then succeeds
    let rule = mock!(Client::get_object)
        .sequence()
        .http_status(503, None)
        .times(2)                                       // Service unavailable for first two calls
        .output(|| GetObjectOutput::builder().build())  // Success on third call
        .build();

    // Create a client with retry enabled
    let client = mock_client!(aws_sdk_s3, [&rule]);

    // The operation should succeed after retries
    let result = client.get_object()
        .bucket("test-bucket")
        .key("test-key")
        .send()
        .await;

    assert!(result.is_ok());
    assert_eq!(rule.num_calls(), 3);  // Called 3 times (2 failures + 1 success)
}

Macros§

  • mock! macro that produces a RuleBuilder from a client invocation
  • mock_client! macro produces a Client configured with a number of Rules and appropriate test default configuration.

Structs§

  • Interceptor which produces mock responses based on a list of rules
  • A rule for matching requests and providing mock responses.
  • A builder for creating rules.

Enums§

  • RuleMode describes how rules will be interpreted.

Functions§