This article discusses the use of the AWS SDK Client Mock for unit testing AWS SDK v3 with Jest, focusing on testing S3.
Abstract
The article begins by introducing the AWS SDK v3 for JavaScript, which was made generally available in December 2020. The new version offers improved typings, a modular architecture, and a new middleware stack, making it easier to use. However, it also necessitated the creation of a new mocking library for unit testing, the AWS SDK Client Mock. The article then delves into a quirk of using this module, which involves using .call(0) to access specific calls made and result[0] to get the index of a result. This seems to suggest that there are bigger responses that can be tested, like calls made to the middleware. The article provides an example of this using code that can be copied and run locally. The code demonstrates the differences between v2 and v3 of the AWS SDK, such as the need to install each client separately and the change in the way commands are sent to the S3 Client. The article concludes by acknowledging that the code for accessing responses is a bit different from using the module for mocking AWS SDK V2, but it is undocumented and may be an edge case.
Bullet points
AWS SDK v3 for JavaScript was made generally available in December 2020, offering improved typings, a modular architecture, and a new middleware stack.
The new version necessitated the creation of a new mocking library for unit testing, the AWS SDK Client Mock.
The article discusses a quirk of using this module, which involves using .call(0) and result[0] to access specific calls made and their results.
The article provides an example of this using code that can be copied and run locally.
The code demonstrates the differences between v2 and v3 of the AWS SDK, such as the need to install each client separately and the change in the way commands are sent to the S3 Client.
The article acknowledges that the code for accessing responses is a bit different from using the module for mocking AWS SDK V2, but it is undocumented and may be an edge case.
Unit tests for AWS SDK v3 with Jest: Testing S3
Introduction
Back in December 2020, version 3 of the AWS SDK for Javascript became generally available. This brought improved typings as it had been rewritten in Typescript. The introduction of a new middleware stack and a modular architecture made it easier to use. We can now import a specific package. In the v2 SDK, we had no choice but to import all the AWS services in one package. But it meant that there was a need for a whole new mocking library for creating unit tests.
Fast forward many months later and an AWS mocking library was built for the Javascript AWS SDK v3. The AWS SDK Client mock.
Feature, not a bug
There is a lot of documentation available about how to use the AWS SDK Client Mock. So I won’t go into the benefits of using this well-written module in this article.
Instead this article will cover one of the quirks of using this module. It seems to be more a feature, rather than a bug. The code looks a bit ugly and seems to mean that it’s incompatible with native Jest calls. I’ll explain shortly. The documentation for the aws-client-mock brings up the idea that we can use .call(0). This will allow us to access a specific call that was made. Or we can use call() to get all calls that were made. It also mentions that result[0] can be used to get the index of a result (as in expect(result[0]).toBe). Natively in Jest it is not necessary to extract all the information from a call. But to me, this seems to be saying that there are bigger responses that can be tested. Like calls made to the middleware. This article will cover an example of this.
The Code
If you want to follow along, the code is below. It can copied and run locally. I have a guide here about how to add Jest Unit Tests to your project.
Line 1: With v3, you don’t have to remember to use the proper import path. Instead, you must install each Client you want to use EG. npm isntall @aws-sdk/client-S3 . Here we’re importing the S3 Client and the PutObject command that we will use
Line 16: Sending our data and command to the S3 Client. This is actually another difference between v2 SDK and v3 SDK. It’s not part of the scope of this article, but is worth mentioning;
In v2 this would be await client.PutObject(params).promise(). In v3 this becomes await client.send(new PutObjectCommand(params))
Line 1: we create an environment variable like as in the code that we’re testing
Line 2: we need to import the aws-sdk-client-mock package. This provides the mocking for the AWS SDK v3 module
Line 3: we then import the modules that we are using for testing. In this unit test it is the S3 Client and S3’s PutObject Command from the AWS SDK v3 package.
Lin 4: we import the code that we are testing
Line 10: it’s good practice to reset the mock after each test, so that stale data is doesn’t affect another test
Line 16: When creating a mock, it is possible to specify mock behaviour on receiving given command. We use a method called .on to do this. In this example it doesn’t need to resolve to anything, because we’re only testing what is sent to the S3 Client. The line is saying “when the S3 Client Mock gets a PutObjectCommand resolve to something”.
Lines 19 to 24: the data we’re using for testing
Line 27: we call the function we’re testing with the test data
Line 28: this is the tricky part that is a bit different from using the module for mocking AWS SDK V2. We need to break down the client object into args[0] and input. We can see how this call is working by changing s3ClientMock.call(0).args[0].input to s3ClientMockCall(0). The object returned is what gets sent to the middleware.
Conclusion
We can see from the code (Line 28 in saveToS3.test.js) that we have to navigate through a call to find a response we want to test. This is not something that we usually have to do with Jest. Maybe this is an issue with Jest, maybe it is an issue with aws-client-sdk. It’s undocumented anyway. Like this bug where the S3 GetObject returns a readable blob rather than an object.
In the documentation, there is this example:
Previously, we would have had to setup a mock jest.fn of snsMock in step before the tests. Then on line 10, we could do something like expect(snsMock).toHaveBeenCalled(2) instead.
A situation like this in the article seems like it could be an edge case and maybe it might be made prettier, and more Jest-like in future iterations.