AWS Developer Tools Blog
Advanced client stubbing in the AWS SDK for Ruby Version 3
The AWS SDK for Ruby provides a robust set of features for stubbing your clients, to make unit tests easier and less fragile. Many of you have used these features to stub your client calls. But in this post, we’re going to explore both a new stub feature in version 3 of the AWS SDK for Ruby, and some advanced stubbing techniques you can add to your testing toolbox.
How to stub a client
The Aws::ClientStubs documentation has several examples of how to write client stubs, but in the most basic form, you do the following:
- Set the
:stub_responses
parameter at client creation. - Write out the stubbed responses you want, sequentially.
For a simple example of stubbing an operation, consider the following.
You can also stub the same operation to provide different responses for sequential calls.
This works pretty well for most test use cases, but it can be fragile in others. We’re expecting that API calls will come in a certain sequence, and for that #put_object
call, the :body
parameter value didn’t matter at all – the stub is fixed. In some cases, we want our stubs to have a bit of dynamic logic, and that’s where dynamic client stubbing is an option.
Dynamic client stubbing
The #stub_responses
method accepts more than static response objects in sequence. You can also provide a Proc
object, which is able to inspect the request context and dynamically determine a response. To take the previous Amazon S3 example, we could have an in-memory bucket that dynamically tracks objects in the database, and can even be pre-seeded.
We’ll take this even further in the final example.
New feature: recorded API requests
While developing the aws-record gem, we discovered that we needed additional testing functionality around client calls. When creating Amazon DynamoDB tables from attribute and metadata specifications, the main thing we wanted to test was that the #create_table
parameters exactly matched what we would have otherwise handcrafted. The stubbed response was mostly irrelevant.
To solve that problem, our tests added a lightweight “handler” that recorded the request parameters. These could then be inspected by our tests and compared to expectations. Starting with version 3.23.0
of aws-sdk-core
, this is now a built-in feature of stubbed SDK clients!
You can access this set of API requests directly from your stubbed client, as follows.
To see how this is used, here is how we’d rewrite one of the Aws::Record::TableConfig
unit tests and its prerequisites.
This is one way to make tests a little less fragile, and test both how you handle client responses (via stubbing) and how you form your requests (via the #api_requests
log of requests made to the client).
Advanced stubbing test example
Let’s bring all of this together into a runnable test file.
Let’s say we’re testing a class that interacts with Amazon S3. We’re performing relatively basic operations around writing and retrieving objects, but don’t want to keep track of which fixed stubs go in which order.
The code below creates a very simple “Fake S3” with an in-memory hash, and implements the #create_bucket
, #get_object
, and #put_object
APIs for their basic parameters. With this, we’re able to verify that our code makes the client calls we intend and handles the responses, without tracking API client call order details as the tests get more complex.
(A caveat: In reality, you likely wouldn’t be testing the client stubbing functionality directly. Instead, you’d be calling into your own functions and then making these checks. However, for example purposes, the file is standalone.)
Conclusion
Client stubs in the AWS SDK for Ruby are a powerful tool for unit testing. They provide a way to test without hitting the network, but allow your code to behave like it’s calling the AWS API clients without having to form mocks for full response objects. This can bring your testing closer to “the real thing” and help you develop code with the SDK with increased confidence.