

# Automatically generate mocks using `mockall` in the AWS SDK for Rust
<a name="testing-automock"></a>

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.