Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add docs for suggested ways to mock AWS requests when testing with this package #1156

Closed
mizzao opened this issue May 7, 2020 · 15 comments
Closed
Labels
documentation This is a problem with documentation. feature-request New feature or enhancement. May require GitHub community feedback.

Comments

@mizzao
Copy link

mizzao commented May 7, 2020

When using the AWS API in a project, it's important to be able to run tests without actually making S3 requests. For many HTTP requests these can be mocked, e.g. with nock. But it's complicated to intercept AWS requests at the HTTP level.

The previous version of the SDK has a pretty well-supported mocking library, aws-sdk-mock. Have you given any thought to how mocking would work for this SDK? Without being able to run tests easily, it'll be hard to switch over and start using this package.

Any documentation or suggestions on how to succinctly set up mocking using this v3 SDK would be great.

@mizzao mizzao added the feature-request New feature or enhancement. May require GitHub community feedback. label May 7, 2020
@monken
Copy link

monken commented May 7, 2020

We started using v3 and have come up with the following:

import 'mocha';
import { expect } from 'chai';
import sinon from 'sinon';

import { SESClient } from '@aws-sdk/client-ses';

describe('sendEmail', () => {
  let stub: any;
  before(() => {
      stub = sinon.stub(SESClient.prototype, 'send');
      after(() => stub.restore());
  });
  describe('send', () => {
      it('can send', async () => {
          await sendEmail(); // uses SESClient internally
          expect(stub.calledOnce).to.be.true;
      });
  });
});

@mizzao
Copy link
Author

mizzao commented May 7, 2020

Cool. For reference, I was able to stub using nock this way, e.g. for a S3 PutObject, adapted from nock/nock#256

before(async () => {
  nock(`https://${bucket}.s3.${region}.amazonaws.com`)	
    .persist()
    .put(/SOME_KEY_REGEXP/)
    .reply(200)
}

after(async () => {
  nock.cleanAll()
  nock.restore()
})

This is intercepting at the HTTP level rather than the method level, but both are fine ways to do it. It would be nice to link to some examples of this when the SDK leaves beta.

@mizzao mizzao changed the title What's the suggested way to mock AWS requests when using this package? Add docs for suggested ways to mock AWS requests when testing with this package May 7, 2020
@trivikr trivikr added the documentation This is a problem with documentation. label May 14, 2020
@gugu
Copy link

gugu commented Jun 1, 2020

I use this to stub it:

describe('Domain registration', () => {
    let sandbox: sinon.SinonSandbox;
    beforeEach(() => {
        sandbox = sinon.createSandbox();
    })
    it('should check domain', async () => {
        sandbox.stub(Route53Domains.prototype, 'checkDomainAvailability').resolves({
            Availability: 'AVAILABLE',
        });
        ....
    });

    afterEach(() => sandbox.restore());
}

It is really great that modules are so simple that no third-party library is needed to mock them, all can be done with sinon/jest

@paulswail
Copy link

paulswail commented Dec 17, 2020

For folks using Jest with the v3 SDK, here's how I mocked out the SES client. A similar approach should work for other clients/client functions:

import '@aws-sdk/client-ses';

const sendEmail = jest.fn().mockImplementation(() => {
  return Promise.resolve({ MessageId: uuid() })
});

jest.mock('@aws-sdk/client-ses', () => {
  return {
    SES: jest.fn().mockImplementation(() => {
      return { sendEmail };
    }),
  };
});

@dmattia
Copy link
Contributor

dmattia commented Jan 22, 2021

The current solutions here work great if you're using the v2 compatible client functions, though they do not work with the recommended methods for the v3 sdk using the send method with multiple commands. Here's what I came up with:

import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
import sinon from 'sinon';

const s3 = new S3Client({})
sinon.stub(s3, 'send')
	.withArgs(sinon.match((param) => param.constructor.name === GetObjectCommand.name)
    .resolves({ VersionId: '1' })
	.withArgs(sinon.match((param) => param.constructor.name === PutObjectCommand.name)
	.resolves({ VersionId: '2' })

@monken
Copy link

monken commented Jan 22, 2021

you can use param instanceof GetObjectCommand instead of param.constructor.name === GetObjectCommand.name.

@allens
Copy link

allens commented Jan 27, 2021

I think aws-mock-sdk could need a fairly significant rewrite to use with the new AWS JS SDK since the API is so different. It uses sinon internally but I wanted a Jest solution since that is what we use for most of our other tests.

Also I wanted to mock the send method rather than the v2 compatible functions.

The aws-doc-sdk-examples repository has some example tests using Jest but they are of the simple toHaveBeenCalled variety rather than mocking the responses.

This is what I'm using for Jest:

const mockSend = jest.fn();
jest.mock('@aws-sdk/client-sqs', () => {
    return {
        ...jest.requireActual('@aws-sdk/client-sqs'),
        SQSClient: function SQSClient(): void {
            this.send = mockSend;
        },
    };
});

describe('JobQueue', () => {
    beforeEach(() => {
        jest.clearAllMocks();
    });

    it('queueJob queues a specified job', async () => {
        
        let message = null;

        mockSend.mockImplementation((command) => {
            if (command instanceof GetQueueUrlCommand) {
                return Promise.resolve({ QueueUrl: 'sqs://queue' });
            } else if (command instanceof SendMessageCommand) {
                message = JSON.parse(command.input.MessageBody);
            }
        });

        // Class which uses SQSClient.send with GetQueueUrlCommand and SendMessageCommand 
        const queue = new JobQueue('queue');
        await queue.queueJob(job);

        expect(message.tenantId).toBe(job.tenantId);
        expect(message.bearerToken).toBe('token');
    });
});

You could probably use jest.spyOn to replace the implementation of send if you only need to mock it within the scope of the test file but I needed to mock the module in another class.

@m-radzikowski
Copy link

This over-typped looking function allows mocking Clients with type-checking for instanceof (for Jest):

import {MetadataBearer} from '@aws-sdk/types';
import {Client, Command, SmithyResolvedConfiguration} from '@aws-sdk/smithy-client';

export const mockSend = <HandlerOptions,
    ClientInput extends object, // eslint-disable-line @typescript-eslint/ban-types
    ClientOutput extends MetadataBearer,
    ResolvedClientConfiguration extends SmithyResolvedConfiguration<HandlerOptions>,
    InputType extends ClientInput,
    OutputType extends ClientOutput>(
    client: new (config: never) => Client<HandlerOptions, ClientInput, ClientOutput, ResolvedClientConfiguration> // eslint-disable-line indent
): jest.Mock<unknown, [Command<InputType, OutputType, ResolvedClientConfiguration>]> => { // eslint-disable-line indent
    const mock = jest.fn<unknown, [Command<InputType, OutputType, ResolvedClientConfiguration>]>();
    (client as jest.Mock).mockImplementation(() => ({
        send: mock,
    }));
    return mock;
};

Usage:

import {AssumeRoleCommand, STSClient} from '@aws-sdk/client-sts';

mockSend(STSClient).mockImplementation(command => {
    if (command instanceof AssumeRoleCommand) {
        const expiration = new Date();
        expiration.setHours(expiration.getHours() + 1);

        return {
            Credentials: {
                AccessKeyId: 'MOCK_ACCESS_KEY_ID',
                SecretAccessKey: 'MOCK_SECRET_ACCESS_KEY',
                SessionToken: 'MOCK_SESSION_TOKEN',
                Expiration: expiration,
            },
        };
    }
});

Usage in tests is simple and provides type-safe instanceof - you will get a warning if you check with a Command class that cannot be used with a given Client. mockSend() returns jest.Mock, so you can use all mockImplementation, mockReturnValue, mockResolvedValue etc.

It would be perfect if also a return type would be checked according to the used Client / Command type, but I don't know if that's even possible.

@m-radzikowski
Copy link

I've created an equivalent of aws-sdk-mock for the v3 SDK: aws-sdk-client-mock

Does not depend on Jest or any other unit testing framework, uses Sinon.js under the hood for highly customizable mocks, fully typed, fluently declared mocks behaviors. Mocking is as simple as that:

mockClient(SNSClient)
    .on(PublishCommand)
    .resolves({
        MessageId: '12345678-4444-5555-6666-111122223333',
    });

but can be further specified to give different responses based on the command input etc.

@allens
Copy link

allens commented Feb 25, 2021

@m-radzikowski https://github.com/m-radzikowski/aws-sdk-client-mock looks really good. Thank you.

@tuscaonline
Copy link

@m-radzikowski very good work! Thank you.

@etiennenoel
Copy link

How would someone go about mocking the new waiters? For example:

    waitForTableExists,
    waitForTableNotExists,

@danobri
Copy link

danobri commented Feb 1, 2022

Has anyone found a way to mock DynamoDBDocument or DynamoDBDocumentClient with just jest? We are trying to upgrade from the v2 Dynamo library, but can't find a way to get the mocks working. The main challenge is finding a way to mock the static "from" constructor.

@AllanZhengYP
Copy link
Contributor

Hi,

We have posted a blog(Mocking modular AWS SDK for JavaScript (v3) in Unit Tests) to show how to use the aws-sdk-client-mock to mock the JavaScript SDK v3.

Please open a new issue if you have more questions regarding this topic.

@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation This is a problem with documentation. feature-request New feature or enhancement. May require GitHub community feedback.
Projects
None yet
Development

No branches or pull requests