Unit testing AWS SDK in TypeScript
Take a look at the source code in GitHub.
Unit testing AWS SDK v3 using aws-sdk-client-mock
I’ll use a simple CRUD application which creates a user account and persists it to DynamoDB using AWS SDK v3 as an example. DynamoDB has a few libraries like @aws-sdk/client-dynamodb
and @aws-sdk/lib-dynamodb
. @aws-sdk/client-dynamodb
is generally used for table level commands such as CreateTableCommand
and DescribeTableCommand
. For CRUD operations, it helpful to use @aws-sdk/lib-dynamodb
which automatically handles marshalling and unmarshalling javascript objects to DynamoDB JSON.
yarn add @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
You can initialise the DynamoDB client in its own file and export it globally so you can use it throughout your application:
We can create a user object and persist it to DynamoDB with the PutCommand
:
To test this we can use the aws-sdk-client-mock library which provides a simple way to mock AWS SDK v3 clients:
yarn add -D aws-sdk-client-mock
In our test file, we can then mock the DynamoDB Document Client that we created earlier.
In our test case, we can setup the expected response from the PutCommand
:
For creating a user, we do not use the response from the PutCommand
so we can just return an empty object.
On the other hand, if we wanted to get a list of users by their role:
We would need to setup the list users that DynamoDB would return:
We also want to assert that we are giving the DynamoDB client commands with the correct parameters, for example with the PutCommand
we want to use the correct table name and the user we expect to create. We can use the custom jest matchers provided by aws-sdk-client-mock-jest to do this:
yarn add -D aws-sdk-client-mock-jest
Make sure to import aws-sdk-client-mock-jest
at the top of your test file:
import 'aws-sdk-client-mock-jest';
Then, we can use the toHaveReceivedCommandWith
matcher to assert that the client has received a PutCommand
with the correct parameters:
Similarly, we can use the toHaveReceivedCommandWith
matcher to assert that the client has received a QueryCommand
with the correct parameters:
Comparing testing approach to AWS SDK v2
aws-sdk-client-mock
makes it very easy to unit test AWS SDK v3. However, the testability of our implementation is highly reliant on aws-sdk-client-mock
. If we look at how our unit tests would have been implemented in AWS SDK v2 where an equivalent library did not exist, we can see that our tests require more setup and understanding of jest
mocking.
This is how we would query users by their role in AWS SDK v2:
Notice that AWS SDK v2 does not support promises initially, so we need to call the promise
method to get a promise. This makes mocking more complex:
We can see above that while it is possible to unit test AWS SDK v2, it was not intuitive how to do so. Ideally, the testability of our application should not be heavily reliant on the implementation details of third party libraries.
Wrapping the AWS SDK client methods
One of the core challenges of testing the AWS SDK library is that the client methods are not easily mockable. jest
tends to be simpler when mocking functions as opposed to classes. We can wrap the AWS SDK v3 client methods in functions which pass the parameters to the client method and return the results.
One of the key considerations is that this wrapper function should not add additional logic, so that we are effectively testing at the same level as before.
Using our wrapper looks like this:
The queryDynamoDb
wrapper function is much easier to mock than the send
method of the DynamoDBDocumentClient
class.
In our test file, we first mock the file that exports the AWS SDK wrapper functions:
jest.mock('../../../aws-sdk-v3/dynamodb');
This replaces all the functions exported by the file with mock functions. Then, we can mock the queryDynamoDb
function using jest.mocked
:
We can also assert that queryDynamoDb
was called with the correct parameters easily with toHaveBeenCalledWith
:
This way our implementation is not reliant on third party libraries like aws-sdk-client-mock
and we can use standard jest
mocking.
If we used AWS SDK v2, queryDynamoDb
would look like this:
But our tests would look almost exactly the same aside from typing differences.
Which can both be simplified using the built-in Parameters utility type:
Using wrapper functions is a great way to abstract some of the implementation details of third party libraries away from our own application. Unfortunately, if we needed to switch to using a different technology for persisting our user objects then you will find that the wrapper functions do not help us because they are inherently tied to DynamoDB.