

# Adding unit testing to your AWS SDK for Rust application
Unit testing

While there are many ways you can implement unit testing in your AWS SDK for Rust project, there are a few that we recommend:
+ [Unit testing using `mockall`](testing-automock.md) – Use `automock` from the `mockall` crate to automatically generate and execute your tests.
+ [Static replay](testing-replay.md) – Use the AWS Smithy runtime's `StaticReplayClient` to create a fake HTTP client that can be used instead of the standard HTTP client that is normally used by AWS services. This client returns the HTTP responses that you specify rather than communicating with the service over the network, so that tests get known data for testing purposes.
+ [Unit testing using `aws-smithy-mocks`](testing-smithy-mocks.md) – Use `mock` and `mock_client` from the `aws-smithy-mocks` crate to mock AWS SDK client responses and to create mock rules that define how the SDK should respond to specific requests.

# Automatically generate mocks using `mockall` in the AWS SDK for Rust
Unit testing using `mockall`

The AWS SDK for Rust provides multiple approaches for testing your code that interacts with AWS services. You can automatically generate the majority of the mock implementations that your tests need by using the popular `[automock](https://docs.rs/mockall/latest/mockall/attr.automock.html)` from the `[mockall](https://docs.rs/mockall/latest/mockall)` crate .

This example tests a custom method called `determine_prefix_file_size()`. This method calls a custom `list_objects()` wrapper method that calls Amazon S3. By mocking `list_objects()`, the `determine_prefix_file_size()` method can be tested without actually contacting Amazon S3. 

1. In a command prompt for your project directory, add the `[mockall](https://docs.rs/mockall/latest/mockall)` crate as a dependency:

   ```
   $ cargo add --dev mockall
   ```

   Using the `--dev` [option](https://doc.rust-lang.org/cargo/commands/cargo-add.html) adds the crate to the `[dev-dependencies]` section of your `Cargo.toml` file. As a [development dependency](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#development-dependencies), it is not compiled and included into your final binary that is used for production code.

   This example code also use Amazon Simple Storage Service as the example AWS service.

   ```
   $ cargo add aws-sdk-s3
   ```

   This adds the crate to the `[dependencies]` section of your `Cargo.toml` file.

1. Include the `automock` module from the `mockall` crate. 

   Also include any other libraries related to the AWS service that you are testing, in this case, Amazon S3.

   ```
   use aws_sdk_s3 as s3;
   #[allow(unused_imports)]
   use mockall::automock;
   
   use s3::operation::list_objects_v2::{ListObjectsV2Error, ListObjectsV2Output};
   ```

1. Next, add code that determines which of two implementation of the application's Amazon S3 wrapper structure to use. 
   + The real one written to access Amazon S3 over the network.
   + The mock implementation generated by `mockall`.

   In this example, the one that's selected is given the name `S3`. The selection is conditional based on the `test` attribute:

   ```
   #[cfg(test)]
   pub use MockS3Impl as S3;
   #[cfg(not(test))]
   pub use S3Impl as S3;
   ```

1. The `S3Impl` struct is the implementation of the Amazon S3 wrapper structure that actually sends requests to AWS.
   + When testing is enabled, this code isn't used because the request is sent to the mock and not AWS. The `dead_code` attribute tells the linter not to report a problem if the `S3Impl` type isn't used.
   +  The conditional `#[cfg_attr(test, automock)]` indicates that when testing is enabled, the `automock` attribute should be set. This tells `mockall` to generate a mock of `S3Impl` that will be named `MockS3Impl`.
   + In this example, the `list_objects()` method is the call you want mocked. `automock` will automatically create an `expect_list_objects()` method for you. 

   ```
   #[allow(dead_code)]
   pub struct S3Impl {
       inner: s3::Client,
   }
   
   #[cfg_attr(test, automock)]
   impl S3Impl {
       #[allow(dead_code)]
       pub fn new(inner: s3::Client) -> Self {
           Self { inner }
       }
   
       #[allow(dead_code)]
       pub async fn list_objects(
           &self,
           bucket: &str,
           prefix: &str,
           continuation_token: Option<String>,
       ) -> Result<ListObjectsV2Output, s3::error::SdkError<ListObjectsV2Error>> {
           self.inner
               .list_objects_v2()
               .bucket(bucket)
               .prefix(prefix)
               .set_continuation_token(continuation_token)
               .send()
               .await
       }
   }
   ```

1. Create the test functions in a module named `test`.
   + The conditional `#[cfg(test)]` indicates that `mockall` should build the test module if the `test` attribute is `true`.

   ```
   #[cfg(test)]
   mod test {
       use super::*;
       use mockall::predicate::eq;
   
       #[tokio::test]
       async fn test_single_page() {
           let mut mock = MockS3Impl::default();
           mock.expect_list_objects()
               .with(eq("test-bucket"), eq("test-prefix"), eq(None))
               .return_once(|_, _, _| {
                   Ok(ListObjectsV2Output::builder()
                       .set_contents(Some(vec![
                           // Mock content for ListObjectsV2 response
                           s3::types::Object::builder().size(5).build(),
                           s3::types::Object::builder().size(2).build(),
                       ]))
                       .build())
               });
   
           // Run the code we want to test with it
           let size = determine_prefix_file_size(mock, "test-bucket", "test-prefix")
               .await
               .unwrap();
   
           // Verify we got the correct total size back
           assert_eq!(7, size);
       }
   
       #[tokio::test]
       async fn test_multiple_pages() {
           // Create the Mock instance with two pages of objects now
           let mut mock = MockS3Impl::default();
           mock.expect_list_objects()
               .with(eq("test-bucket"), eq("test-prefix"), eq(None))
               .return_once(|_, _, _| {
                   Ok(ListObjectsV2Output::builder()
                       .set_contents(Some(vec![
                           // Mock content for ListObjectsV2 response
                           s3::types::Object::builder().size(5).build(),
                           s3::types::Object::builder().size(2).build(),
                       ]))
                       .set_next_continuation_token(Some("next".to_string()))
                       .build())
               });
           mock.expect_list_objects()
               .with(
                   eq("test-bucket"),
                   eq("test-prefix"),
                   eq(Some("next".to_string())),
               )
               .return_once(|_, _, _| {
                   Ok(ListObjectsV2Output::builder()
                       .set_contents(Some(vec![
                           // Mock content for ListObjectsV2 response
                           s3::types::Object::builder().size(3).build(),
                           s3::types::Object::builder().size(9).build(),
                       ]))
                       .build())
               });
   
           // Run the code we want to test with it
           let size = determine_prefix_file_size(mock, "test-bucket", "test-prefix")
               .await
               .unwrap();
   
           assert_eq!(19, size);
       }
   }
   ```
   + Each test uses `let mut mock = MockS3Impl::default();` to create a `mock` instance of `MockS3Impl`. 
   + It uses the mock's `expect_list_objects()` method (which was created automatically by `automock`) to set the expected result for when the `list_objects()` method is used elsewhere in the code.
   + After the expectations are established, it uses these to test the function by calling `determine_prefix_file_size()`. The returned value is checked to confirm that it's correct, using an assertion.

1. The `determine_prefix_file_size()` function uses the Amazon S3 wrapper to get the size of the prefix file:

   ```
   #[allow(dead_code)]
   pub async fn determine_prefix_file_size(
       // Now we take a reference to our trait object instead of the S3 client
       // s3_list: ListObjectsService,
       s3_list: S3,
       bucket: &str,
       prefix: &str,
   ) -> Result<usize, s3::Error> {
       let mut next_token: Option<String> = None;
       let mut total_size_bytes = 0;
       loop {
           let result = s3_list
               .list_objects(bucket, prefix, next_token.take())
               .await?;
   
           // Add up the file sizes we got back
           for object in result.contents() {
               total_size_bytes += object.size().unwrap_or(0) as usize;
           }
   
           // Handle pagination, and break the loop if there are no more pages
           next_token = result.next_continuation_token.clone();
           if next_token.is_none() {
               break;
           }
       }
       Ok(total_size_bytes)
   }
   ```

The type `S3` is used to call the wrapped SDK for Rust functions to support both `S3Impl` and `MockS3Impl` when making HTTP requests. The mock automatically generated by `mockall` reports any test failures when testing is enabled.

You can [view the complete code for these examples](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/rustv1/examples/testing) on GitHub.

# Simulate HTTP traffic using static replay in the AWS SDK for Rust
Static replay

The AWS SDK for Rust provides multiple approaches for testing your code that interacts with AWS services. This topic describes how to use the `StaticReplayClient` to create a fake HTTP client that can be used instead of the standard HTTP client that is normally used by AWS services. This client returns the HTTP responses that you specify rather than communicating with the service over the network, so that tests get known data for testing purposes.

The `aws-smithy-http-client` crate includes a test utility class called [https://docs.rs/aws-smithy-http-client/latest/aws_smithy_http_client/test_util/struct.StaticReplayClient.html](https://docs.rs/aws-smithy-http-client/latest/aws_smithy_http_client/test_util/struct.StaticReplayClient.html). This HTTP client class can be specified instead of the default HTTP client when creating an AWS service object.

When initializing the `StaticReplayClient`, you provide a list of HTTP request and response pairs as `ReplayEvent` objects. While the test is running, each HTTP request is recorded and the client returns the next HTTP response found in the next `ReplayEvent` in the event list as the HTTP client's response. This lets the test run using known data and without a network connection.

## Using static replay


To use static replay, you don't need to use a wrapper. Instead, determine what the actual network traffic should look like for the data your test will use, and provide that traffic data to the `StaticReplayClient` to use each time the SDK issues a request from the AWS service client.

**Note**  
There are several ways to collect the expected network traffic, including the AWS CLI and many network traffic analyzers and packet sniffer tools.
+ Create a list of `ReplayEvent` objects that specify the expected HTTP requests and the responses that should be returned for them.
+ Create a `StaticReplayClient` using the HTTP transaction list created in the previous step.
+ Create a configuration object for the AWS client, specifying the `StaticReplayClient` as the `Config` object's `http_client`.
+ Create the AWS service client object, using the configuration created in the previous step.
+ Perform the operations that you want to test, using the service object that's configured to use the `StaticReplayClient`. Each time the SDK sends an API request to AWS, the next response in the list is used.
**Note**  
The next response in the list is always returned, even if the sent request doesn't match the one in the vector of `ReplayEvent` objects.
+ When all the desired requests have been made, call the `StaticReplayClient.assert_requests_match()` function to verify that the requests sent by the SDK match the ones in the list of `ReplayEvent` objects.

## Example


Let's look at the tests for the same `determine_prefix_file_size()` function in the previous example, but using static replay instead of mocking.

1. In a command prompt for your project directory, add the [https://crates.io/crates/aws-smithy-http-client](https://crates.io/crates/aws-smithy-http-client) crate as a dependency:

   ```
   $ cargo add --dev aws-smithy-http-client --features test-util
   ```

   Using the `--dev` [option](https://doc.rust-lang.org/cargo/commands/cargo-add.html) adds the crate to the `[dev-dependencies]` section of your `Cargo.toml` file. As a [development dependency](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#development-dependencies), it is not compiled and included into your final binary that is used for production code.

   This example code also use Amazon Simple Storage Service as the example AWS service.

   ```
   $ cargo add aws-sdk-s3
   ```

   This adds the crate to the `[dependencies]` section of your `Cargo.toml` file.

1. In your test code module, include both of the types that you'll need.

   ```
   use aws_smithy_http_client::test_util::{ReplayEvent, StaticReplayClient};
   use aws_sdk_s3::primitives::SdkBody;
   ```

1. The test begins by creating the `ReplayEvent` structures representing each of the HTTP transactions that should take place during the test. Each event contains an HTTP request object and an HTTP response object representing the information that the AWS service would normally reply with. These events are passed into a call to `StaticReplayClient::new()`:

   ```
           let page_1 = ReplayEvent::new(
                   http::Request::builder()
                       .method("GET")
                       .uri("https://test-bucket.s3.us-east-1.amazonaws.com/?list-type=2&prefix=test-prefix")
                       .body(SdkBody::empty())
                       .unwrap(),
                   http::Response::builder()
                       .status(200)
                       .body(SdkBody::from(include_str!("./testing/response_multi_1.xml")))
                       .unwrap(),
               );
           let page_2 = ReplayEvent::new(
                   http::Request::builder()
                       .method("GET")
                       .uri("https://test-bucket.s3.us-east-1.amazonaws.com/?list-type=2&prefix=test-prefix&continuation-token=next")
                       .body(SdkBody::empty())
                       .unwrap(),
                   http::Response::builder()
                       .status(200)
                       .body(SdkBody::from(include_str!("./testing/response_multi_2.xml")))
                       .unwrap(),
               );
           let replay_client = StaticReplayClient::new(vec![page_1, page_2]);
   ```

   The result is stored in `replay_client`. This represents an HTTP client that can then be used by the SDK for Rust by specifying it in the client's configuration.

1. To create the Amazon S3 client, call the client class's `from_conf()` function to create the client using a configuration object:

   ```
           let client: s3::Client = s3::Client::from_conf(
               s3::Config::builder()
                   .behavior_version(BehaviorVersion::latest())
                   .credentials_provider(make_s3_test_credentials())
                   .region(s3::config::Region::new("us-east-1"))
                   .http_client(replay_client.clone())
                   .build(),
           );
   ```

   The configuration object is specified using the builder's `http_client()` method, and the credentials are specified using the `credentials_provider()` method. The credentials are created using a function called `make_s3_test_credentials()`, which returns a fake credentials structure:

   ```
   fn make_s3_test_credentials() -> s3::config::Credentials {
       s3::config::Credentials::new(
           "ATESTCLIENT",
           "astestsecretkey",
           Some("atestsessiontoken".to_string()),
           None,
           "",
       )
   }
   ```

   These credentials don't need to be valid because they won't actually be sent to AWS.

1. Run the test by calling the function that needs testing. In this example, that function's name is `determine_prefix_file_size()`. Its first parameter is the Amazon S3 client object to use for its requests. Therefore, specify the client created using the `StaticReplayClient` so requests are handled by that rather than going out over the network:

   ```
           let size = determine_prefix_file_size(client, "test-bucket", "test-prefix")
               .await
               .unwrap();
   
           assert_eq!(19, size);
   
           replay_client.assert_requests_match(&[]);
   ```

   When the call to `determine_prefix_file_size()` is finished, an assert is used to confirm that the returned value matches the expected value. Then, the `StaticReplayClient` method `assert_requests_match()` function is called. This function scans the recorded HTTP requests and confirms that they all match the ones specified in the array of `ReplayEvent` objects provided when creating the replay client.

You can [view the complete code for these examples](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/rustv1/examples/testing) on GitHub.

# Unit testing with `aws-smithy-mocks` in the AWS SDK for Rust
Unit testing using `aws-smithy-mocks`

The AWS SDK for Rust provides multiple approaches for testing your code that interacts with AWS services. This topic describes how to use the [https://docs.rs/aws-smithy-mocks/latest/aws_smithy_mocks/](https://docs.rs/aws-smithy-mocks/latest/aws_smithy_mocks/) crate, which offers a simple yet powerful way to mock AWS SDK client responses for testing purposes.

## Overview


When writing tests for code that uses AWS services, you often want to avoid making actual network calls. The `aws-smithy-mocks` crate provides a solution by allowing you to:
+ Create mock rules that define how the SDK should respond to specific requests.
+ Return different types of responses (success, error, HTTP responses).
+ Match requests based on their properties.
+ Define sequences of responses for testing retry behavior.
+ Verify that your rules were used as expected.

## Adding the dependency


In a command prompt for your project directory, add the [https://crates.io/crates/aws-smithy-mocks](https://crates.io/crates/aws-smithy-mocks) crate as a dependency:

```
$ cargo add --dev aws-smithy-mocks
```

Using the `--dev` [option](https://doc.rust-lang.org/cargo/commands/cargo-add.html) adds the crate to the `[dev-dependencies]` section of your `Cargo.toml` file. As a [development dependency](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#development-dependencies), it is not compiled and included into your final binary that is used for production code.

This example code also use Amazon Simple Storage Service as the example AWS service, and requires feature `test-util`.

```
$ cargo add aws-sdk-s3 --features test-util
```

This adds the crate to the `[dependencies]` section of your `Cargo.toml` file.

## Basic usage


 Here's a simple example of how to use `aws-smithy-mocks` to test code that interacts with Amazon Simple Storage Service (Amazon S3):

```
use aws_sdk_s3::operation::get_object::GetObjectOutput;
use aws_sdk_s3::primitives::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!(aws_sdk_s3::Client::get_object)
        .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 mock rules


Rules are created using the `mock!` macro, which takes a client operation as an argument. You can then configure how the rule should behave. 

### Matching Requests


 You can make rules more specific by matching on request properties:

```
let 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()
    });
```

### Different Response Types


You can return different types of responses:

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

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

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

## Testing retry behavior


One of the most powerful features of `aws-smithy-mocks` is the ability to test retry behavior by defining sequences of responses:

```
// Create a rule that returns 503 twice, then succeeds
let retry_rule = mock!(aws_sdk_s3::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();
```

## Rule modes


You can control how rules are matched and applied using rule modes:

```
// Sequential mode: Rules are tried in order, and when a rule is exhausted, the next rule is used
let client = mock_client!(aws_sdk_s3, RuleMode::Sequential, [&rule1, &rule2]);

// MatchAny mode: The first matching rule is used, regardless of order
let client = mock_client!(aws_sdk_s3, RuleMode::MatchAny, [&rule1, &rule2]);
```

## Example: Testing retry behavior


Here's a more complete example showing how to test retry behavior:

```
use aws_sdk_s3::operation::get_object::GetObjectOutput;
use aws_sdk_s3::config::RetryConfig;
use aws_sdk_s3::primitives::ByteStream;
use aws_smithy_mocks::{mock, mock_client, RuleMode};

#[tokio::test]
async fn test_retry_behavior() {
    // Create a rule that returns 503 twice, then succeeds
    let retry_rule = mock!(aws_sdk_s3::Client::get_object)
        .sequence()
        .http_status(503, None)
        .times(2)
        .output(|| GetObjectOutput::builder()
            .body(ByteStream::from_static(b"success"))
            .build())
        .build();

    // Create a mocked client with the rule and custom retry configuration
    let s3 = mock_client!(
        aws_sdk_s3,
        RuleMode::Sequential,
        [&retry_rule],
        |client_builder| {
            client_builder.retry_config(RetryConfig::standard().with_max_attempts(3))
        }
    );

    // This should succeed after two retries
    let result = s3
        .get_object()
        .bucket("test-bucket")
        .key("test-key")
        .send()
        .await
        .expect("success after retries");

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

    // Verify all responses were used
    assert_eq!(retry_rule.num_calls(), 3);
}
```

## Example: Different responses based on request parameters


You can also create rules that return different responses based on request parameters:

```
use aws_sdk_s3::operation::get_object::{GetObjectOutput, GetObjectError};
use aws_sdk_s3::types::error::NoSuchKey;
use aws_sdk_s3::Client;
use aws_sdk_s3::primitives::ByteStream;
use aws_smithy_mocks::{mock, mock_client, RuleMode};

#[tokio::test]
async fn test_different_responses() {
    // Create rules for different request parameters
    let exists_rule = mock!(Client::get_object)
        .match_requests(|req| req.bucket() == Some("test-bucket") && req.key() == Some("exists"))
        .sequence()
        .output(|| GetObjectOutput::builder()
            .body(ByteStream::from_static(b"found"))
            .build())
        .build();

    let not_exists_rule = mock!(Client::get_object)
        .match_requests(|req| req.bucket() == Some("test-bucket") && req.key() == Some("not-exists"))
        .sequence()
        .error(|| GetObjectError::NoSuchKey(NoSuchKey::builder().build()))
        .build();

    // Create a mocked client with the rules in MatchAny mode
    let s3 = mock_client!(aws_sdk_s3, RuleMode::MatchAny, [&exists_rule, &not_exists_rule]);

    // Test the "exists" case
    let result1 = s3
        .get_object()
        .bucket("test-bucket")
        .key("exists")
        .send()
        .await
        .expect("object exists");

    let data = result1.body.collect().await.expect("successful read").to_vec();
    assert_eq!(data, b"found");

    // Test the "not-exists" case
    let result2 = s3
        .get_object()
        .bucket("test-bucket")
        .key("not-exists")
        .send()
        .await;

    assert!(result2.is_err());
    assert!(matches!(result2.unwrap_err().into_service_error(),
                    GetObjectError::NoSuchKey(_)));
}
```

## Best practices


When using `aws-smithy-mocks` for testing:

1.  Match specific requests: Use `match_requests()` to ensure your rules only apply to the intended requests, in particular with `RuleMode:::MatchAny`.

1.  Verify rule usage: Check `rule.num_calls()` to ensure your rules were actually used.

1.  Test error handling: Create rules that return errors to test how your code handles failures.

1.  Test retry logic: Use response sequences to verify that your code correctly handles any custom retry classifiers or other retry behavior.

1. Keep tests focused: Create separate tests for different scenarios rather than trying to cover everything in one test.