Unverified Commit 61007da6 authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Add configuration ability to `mock_client` macro (#4001)

## Motivation and Context
We've seen folks who need to be able to modify configuration of the
client, but there's no way to deal with it outside of ejecting from the
macro.

## Description
- add configure option to the macro
- improve some docs

## Testing
I ran those doc tests unignored

## Checklist
<!--- If a checkbox below is not applicable, then please DELETE it
rather than leaving it unchecked -->
- [ ] For changes to the smithy-rs codegen or runtime crates, I have
created a changelog entry Markdown file in the `.changelog` directory,
specifying "client," "server," or both in the `applies_to` key.
- [ ] For changes to the AWS SDK, generated SDK code, or SDK runtime
crates, I have created a changelog entry Markdown file in the
`.changelog` directory, specifying "aws-sdk-rust" in the `applies_to`
key.

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
parent 935c2651
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
[package]
name = "aws-smithy-mocks-experimental"
version = "0.2.1"
version = "0.2.2"
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>"]
description = "Experimental testing utilities for smithy-rs generated clients"
edition = "2021"
+36 −1
Original line number Diff line number Diff line
# aws-smithy-mocks

Experiment for mocking Smithy Clients using interceptors. See [`tests/get-object-mocks.rs`](tests/get-object-mocks.rs) for example usage.
This package allows testing clients generated by smithy-rs (including all packages of the AWS Rust SDK) by using interceptors to return stub responses. This approach is quite useful for testing both happy-path and simple error scenarios and avoids the need for mocking the entire client or using traits.

As an example, consider this simple usage with S3:

```rust
#[tokio::test]
async fn test_s3() {
     let s3_real_object = mock!(Client::get_object).then_output(|| {
         GetObjectOutput::builder()
             .body(ByteStream::from_static(b"test-test-test"))
             .build()
     });
    let s3 = mock_client!(aws_sdk_s3, [&s3_real_object]);
    let data = s3
        .get_object()
        .bucket("test-bucket")
        .key("correct-key")
        .send()
        .await
        .expect("success response")
        .body
        .collect()
        .await
        .expect("successful read")
        .to_vec();
    assert_eq!(data, b"test-test-test");
    assert_eq!(s3_real_object.num_calls(), 1);
}
```

You can find more examples in the `tests` folder of this crate.

## Shortcomings of this approach
This approach is not well suited for testing precise error handling, especially when considering retries or interactions with HTTP responses—This approach hijacks the request response flow entirely and is not a faithful model in these cases.

If you need to test behavior around retries or connection management, you should use HTTP-connection based mocking instead.

<!-- anchor_start:footer -->
This crate is part of the [AWS SDK for Rust](https://awslabs.github.io/aws-sdk-rust/) and the [smithy-rs](https://github.com/smithy-lang/smithy-rs) code generator.
+35 −5
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@
 * SPDX-License-Identifier: Apache-2.0
 */

//! This crate allows mocking of smithy clients.

/* Automatically managed default lints */
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
/* End of automatically managed default lints */
@@ -94,22 +96,50 @@ macro_rules! mock {
///   .then_error(||GetObjectError::NoSuchKey(NoSuchKey::builder().build()));
/// let client = mock_client!(aws_sdk_s3, RuleMode::Sequential, &[&get_object_error_path, &get_object_happy_path]);
/// ```
///
/// **Create a client but customize a specific setting**:
/// ```rust,ignore
/// use aws_sdk_s3::operation::get_object::GetObjectOutput;
/// use aws_sdk_s3::Client;
/// use aws_smithy_types::byte_stream::ByteStream;
/// use aws_smithy_mocks_experimental::{mock_client, mock, RuleMode};
/// let get_object_happy_path = 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"12345-abcde")).build());
/// let client = mock_client!(
///     aws_sdk_s3,
///     RuleMode::Sequential,
///     &[&get_object_happy_path],
///     // Perhaps you need to force path style
///     |client_builder|client_builder.force_path_style(true)
/// );
/// ```
///
#[macro_export]
macro_rules! mock_client {
    ($aws_crate: ident, $rules: expr) => {
        mock_client!($aws_crate, $crate::RuleMode::Sequential, $rules)
    };
    ($aws_crate: ident, $rule_mode: expr, $rules: expr) => {{
        mock_client!($aws_crate, $rule_mode, $rules, |conf| conf)
    }};
    ($aws_crate: ident, $rule_mode: expr, $rules: expr, $additional_configuration: expr) => {{
        let mut mock_response_interceptor =
            $crate::MockResponseInterceptor::new().rule_mode($rule_mode);
        for rule in $rules {
            mock_response_interceptor = mock_response_interceptor.with_rule(rule)
        }
        // allow callers to avoid explicitly specifying the type
        fn coerce<T: Fn($aws_crate::config::Builder) -> $aws_crate::config::Builder>(f: T) -> T {
            f
        }
        $aws_crate::client::Client::from_conf(
            coerce($additional_configuration)(
                $aws_crate::config::Config::builder()
                    .with_test_defaults()
                    .region($aws_crate::config::Region::from_static("us-east-1"))
                .interceptor(mock_response_interceptor)
                    .interceptor(mock_response_interceptor),
            )
            .build(),
        )
    }};