How to Mock AWS Services in TypeScript

Kyle Galbraith - Sep 10 '18 - - Dev Community

I've recently been working on a new project to automatically convert blog posts to audio that has a couple different serverless microservices.

Each microservice is initialized using the Serverless Framework and typically consists of one or more Lambda functions and either an SNS topic, SQS queue, DynamoDB table, or API Gateway endpoint.

I decided to implement each serverless microservice using TypeScript rather than my default of traditional Javascript. I chose this path because I wanted to have static type checking for a more resilient code base.

Typescript is a very cool language and has grown in popularity in recent years. It provides a lot of different benefits outside of just static type checking:

  • Optional typings.
  • Type inference.
  • Access to new ES features.
  • Option to compile to different JS versions.
  • Clear library API definitions via .d.ts.
  • Class and module support.
  • A lot of great tooling to support the environment.

In building out these services I ran into a problem that stopped me dead in my tracks for a few hours. I was writing unit tests for a new adapter inside of one of my services that leverages DynamoDB for storage.

To effectively test the adapter I needed to mock the responses from the DynamoDB client inside of the aws-sdk. This proved to be a bit more challenging than I originally thought.

Therefore I thought it would be worthwhile to put together a quick blog post on the two methods I found for mocking the aws-sdk.

Mocking in TypeScript with TypeMoq

There are a lot of fantastic tools for mocking TypeScript. The one I chose for my work was TypeMoq. It is very similar to the Moq library in the .NET framework.

It is fairly simple to get started. Let's say we have a piece of code like the one shown below.

export class MessageProcessor {
    conversionAdapter: ConversionAdapter

    constructor(conversionAdapter: ConversionAdapter) {
        this.conversionAdapter = conversionAdapter;
    }

    processJobs(records: IConversionModel[]): Promise<boolean>  {
        return new Promise((resolve, reject) => {
            this.conversionAdapter.convert(record).then((result) => {
                resolve(true);
            }).catch((err) => {
                reject(err);
            });
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

This code is calling the convert method on a class named ConversionAdapter. From the code, we can see that the method returns a Promise. We use the result of that promise to resolve our outer promise or reject it if there is an error.

We can set up a test for the processJobs method by using TypeMoq to mock the result of the convert method on ConversionAdapter.

describe("MessageProcessor", function () {
    before(function () => {
        this.model = new ConversionModel();
        this.model.url = "url";
        this.model.content = "content";
        this.model.title = "title";
        this.model.userId = "1234-userid";
    });

    it("processJobs success", function (done) => {
        var mockAdapterFactory = TypeMoq.Mock.ofType<IConversionAdapter>(ConversionAdapter);
        mockAdapterFactory.setup(m => m.convert(this.model)).returns(() => Promise.resolve(1));

        var processor = new MessageProcessor(mockAdapterFactory.object);
        var pass = [ this.model ];
        processor.processJobs(pass).then((result) => {
            expect(result).to.eql(true);
            done();
        });
    });
});
Enter fullscreen mode Exit fullscreen mode

The key here is our setup of mockAdapterFactory. We are using TypeMoq.Mock.ofType<IConversionAdapter>(ConversionAdapter) to create a mock of our adapter. We then set up the convert method to return Promise.resolve(1).

From there it's just a matter of us passing the actual mock, mockAdapterFactory.object into our MessageProcessor class.

Now we can wait for our promise to resolve and verify that the result we get back is true.

OK, now we got the basics of mocking in Typescript. Let's move onto how we can mock AWS services like SNS, SQS, or DynamoDB.

Two ways to mock AWS services in TypeScript

To be clear, there is more than these two ways to mock the aws-sdk in TypeScript. These are just two ways I got working and wanted to share my experiences to hopefully save someone time in the future.

Let's give ourselves some context first. Here we have a class, StorageAdapter, that is using DynamoDB internally.

export class StorageAdapter implements IStorageAdapter {
    dynamoClient: DynamoDB;
    tableName: string;

    constructor(dynamoClient: DynamoDB, tableName: string) {
        this.tableName = tableName;
        this.dynamoClient = dynamoClient;
    }

    readItem(id: string): Promise<DynamoDB.GetItemOutput> {
        return new Promise((resolve, reject) => {
            let itemInput: DynamoDB.Types.GetItemInput = {
                TableName: this.tableName,
                Key: this.setKey(id)
            };

            this.dynamoClient.getItem(itemInput).promise().then((data: PromiseResult<DynamoDB.GetItemOutput, AWSError>) => {
                if (data.$response && data.$response.error != null) {
                    reject(data.$response.error);
                } else {
                    resolve(data.Item);
                }
            });
        });
    }

    private setKey(id: string): Key {
        let keyStruct: Key = {
            "ConversionId": {
                S: id
            }
        }

        return keyStruct;
    }
}
Enter fullscreen mode Exit fullscreen mode

Here in readItem, we are calling getItem on the DynamoDB client from the aws-sdk. Additionally, we tell the SDK that we want a promise returned to us by appending .promise() at the end of our call.

We then take the result of that call and reject or resolve our outer promise depending on whether there is an error.

Now, let's dive into two ways we can mock getItem on the DynamoDB client in order to properly test our code.

Method 1: Using TypeMoq

Our first option is to use TypeMoq.

describe("StorageAdapter", function () => {
    before(function () => {
    });

    it("readItem correctly", function (done) => {
        var expectedPr: PromiseResult<DynamoDB.GetItemOutput, AWSError> = {
            Item: {
                "ConversionId": {
                    S: "fooid"
                },
                "Email": {
                    S: "kyle@test.com"
                },
                "Status": {
                    S: "Converting"
                },
                "Characters": {
                    N: `${123}`
                },
                "Url": {
                    S: "https://cloudfront.com"
                }
            },
            $response: null
        };

        var mockAdapterFactory = TypeMoq.Mock.ofType<DynamoDB>(DynamoDB);
        var mockRequestFactory = TypeMoq.Mock.ofType<Request<DynamoDB.GetItemOutput, AWSError>>(Request);      

        mockRequestFactory.setup(m => m.promise()).returns(() => Promise.resolve(expectedPr));
        mockAdapterFactory.setup(m => m.getItem(TypeMoq.It.isAny())).returns(() => mockRequestFactory.object);

        var storage = new StorageAdapter(mockAdapterFactory.object, "testTable");
        storage.readItem("fooid").then((result) => {
            console.log(result);
        }).catch((err) => {
            console.error(err);
        });
    });
});

Enter fullscreen mode Exit fullscreen mode

We first have to set up our DynamoDB mock which is what we see with mockAdapterFactory. Then we have to create a mocked Request object that getItem returns and we can see that with our mockRequestFactory variable.

Now we can configure our mocks to return what we need to test our code.

First, we configure promise() on mockRequestFactory to return a resolved promise with our expected DynamoDB.GetItemOutput declared in expectedPR.

Next, we configure getItem() on mockAdapterFactory to return the mocked request we just finished setting up.

From there we just need to pass the actual mock of our DynamoDB client by passing mockAdapterFactory.object into our StorageAdapter.

If your keeping track we need four lines of code using TypeMoq in order to mock our getItem call on DynamoDB. This is very verbose for writing tests, but it does provide a high level of visibility into what types are being leveraged for this getItem call to complete successfully.

Let's explore how we can accomplish the same goal using a different mocking library.

Method 2: Using aws-sdk-mock

There is another mocking library, aws-sdk-mock, that is dedicated to mocking the aws-sdk.

We can actually reduce the four lines of code we have with TypeMoq to just one line of code.

describe("StorageAdapter", () => {
    before(() => {
    });

    it("readItem correctly", () => {
        var expectedPr: PromiseResult<DynamoDB.GetItemOutput, AWSError> = {
            Item: {
                "ConversionId": {
                    S: "fooid"
                },
                "Email": {
                    S: "kyle@test.com"
                },
                "Status": {
                    S: "Converting"
                },
                "Characters": {
                    N: `${123}`
                },
                "Url": {
                    S: "https://cloudfront.com"
                }
            },
            $response: null
        };

        AWSMock.mock('DynamoDB', 'getItem', Promise.resolve(expectedPr));
        var storage = new StorageAdapter(new DynamoDB(), "testTable");
        storage.readItem("fooid").then((result) => {
            console.log(result);
        }).catch((err) => {
            var x = 3;
        });
    });
});

Enter fullscreen mode Exit fullscreen mode

Instead of mocking our DynamoDB client, the request, and the promise associated with getItem, we can just mock getItem and return a resolved promise. There is a lot less set up in order to create a valuable mock and accurately test our StorageAdapter class.

This has the benefit of being very concise and clear to follow. However, currently aws-sdk-mock does not have any TypeScript typings which impacts our IntelliSense during development.

Recapping mocking AWS in TypeScript

There have been quite a few articles that cover how to mock AWS services, but they often don't incorporate TypeScript. This isn't a problem as anything written for normal JavaScript can be leveraged in our TS world, just without any types. This is what we see with our use of aws-sdk-mock.

Testing is key to any production-ready service, even in your low-level adapters that interface with AWS services. However, it can be deceptively tricky to mock AWS services in order to accurately test our code. This is often because the types are more complex, for example, .getItem().promise() actually returns a generic type of PromiseResult<DynamoDB.GetItemOutput, AWSError>.

What we have demonstrated here, is two approaches to mocking the aws-sdk. Our first approach using TypeMoq demonstrated a TypeScript style, using types and incrementally building up our mock. We build up the entire chain of types from the request all the way up to the resolved promise.

The second approach using aws-sdk-mock showed how we can mock a single service call with one line of code.

In method one, we have types and verbosity, but sacrifice conciseness by having to build up our mock. Method two, we have conciseness but lose our types and must then know the specific method calls and client declarations. There is no right answer here, both work great depending on what your preference is.

On another note, aws-sdk-mock is dedicated to mocking just the aws-sdk. Whereas TypeMoq can be used universally throughout our TypeScript tests. In fact, I ended up using both throughout my services. TypeMoq to mock my classes as well as my interfaces and the SDK mock for mocking out the AWS services.

Like most things in developing code, there isn't a single right answer. My hope is that my experience in mocking the aws-sdk in TypeScript has given you an example of how you can do the same in your own code.

As always if you have questions or comments related to AWS or this post, please leave a comment below.

Are you hungry to learn even more about Amazon Web Services?

If you are looking to begin your AWS journey but feel lost on where to start, consider checking out my course. We focus on hosting, securing, and deploying static websites on AWS. Allowing us to learn over 6 different AWS services as we are using them. After you have mastered the basics there we can then dive into two bonus chapters to cover more advanced topics like Infrastructure as Code and Continous Deployment.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .