Search code examples
node.jsunit-testingjestjsaws-sdkaws-sdk-nodejs

Mocking AWS SES with Jest


I am trying to mock AWS SES in Jest but continue to get this timeout error:

Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:

I've stripped out the code that is unrelated and verified to be working. Here is the code that uses SES:

import SES from 'aws-sdk/clients/ses';

try {
    /** Initialize SES Class */
    const ses = new SES({ apiVersion: '2010-12-01' });

    await ses.sendTemplatedEmail(sesEmailParams).promise();
} catch(err) {
    return next(internalErrorMessage);
}

Here is the test that uses SES:

import AWS from 'aws-sdk';

test('Should error when ses.sendTemplatedEmail.promise() fails', async (done) => {
    const fakeSesPromise = {
      promise: jest
        .fn()
        .mockImplementationOnce(() => Promise.reject(new Error('This is an SES error'))),
    };

    const fakeSes = {
      sendTemplatedEmail: () => {
        return fakeSesPromise;
      },
    };

    AWS.SES = jest.fn(() => fakeSes);

    await user.forgotPassword(mockRequest, mockResponse, mockNext);

    expect(fakeSesPromise).toHaveBeenCalledTimes(1);
    expect(mockNext).toHaveBeenCalledWith(internalErrorMessage);

    done();
});

I've tried a few other suggested ways which ended with the same result. I am assuming that it has something to do with how the aws-sdk uses the .promise() function.

Any help would be much appreciated!

Update #1:

The below solution from @slideshowp2 works, but will throw this Typescript error:

Property 'mockRejectedValueOnce' does not exist on type '() => Promise<PromiseResult<SendTemplatedEmailResponse, AWSError>>'

for this line:

mockSes.sendTemplatedEmail().promise.mockRejectedValueOnce(new Error('This is an SES error'));

To make it work, just change this line:

const mockSes = new mockSES();

to:

const mockSes = (new SES() as unknown) as { sendTemplatedEmail: jest.Mock; promise: jest.Mock };

Solution

  • Here is a unit test solution using jest.mock(moduleName, factory, options), mock aws-sdk/clients/ses module, SES class and its methods.

    E.g.

    user.js:

    import SES from 'aws-sdk/clients/ses';
    
    const internalErrorMessage = 'internalErrorMessage';
    
    export const user = {
      async forgotPassword(req, res, next) {
        const sesEmailParams = {
          Source: 'Sender Name <sender@recipient.com>',
          Destination: {
            ToAddresses: [],
          },
          Template: 'tpl',
          TemplateData: 'data',
        };
        try {
          const ses = new SES({ apiVersion: '2010-12-01' });
          await ses.sendTemplatedEmail(sesEmailParams).promise();
        } catch (err) {
          return next(internalErrorMessage);
        }
      },
    };
    

    user.test.js:

    import MockSES from 'aws-sdk/clients/ses';
    import { user } from './user';
    
    jest.mock('aws-sdk/clients/ses', () => {
      const mSES = {
        sendTemplatedEmail: jest.fn().mockReturnThis(),
        promise: jest.fn(),
      };
      return jest.fn(() => mSES);
    });
    
    describe('61491519', () => {
      test('Should error when ses.sendTemplatedEmail.promise() fails', async () => {
        const mSes = new MockSES();
        const mError = new Error('This is an SES error');
        mSes.sendTemplatedEmail().promise.mockRejectedValueOnce(mError);
        const mockRequest = {};
        const mockResponse = {};
        const mockNext = jest.fn();
        await user.forgotPassword(mockRequest, mockResponse, mockNext);
    
        expect(MockSES).toBeCalledWith({ apiVersion: '2010-12-01' });
        expect(mSes.sendTemplatedEmail).toBeCalledWith({
          Source: 'Sender Name <sender@recipient.com>',
          Destination: {
            ToAddresses: [],
          },
          Template: 'tpl',
          TemplateData: 'data',
        });
        expect(mSes.sendTemplatedEmail().promise).toBeCalledTimes(1);
        expect(mockNext).toHaveBeenCalledWith('internalErrorMessage');
      });
    });
    

    unit test results with 100% coverage:

     PASS  stackoverflow/61491519/user.test.js (10.071s)
      61491519
        ✓ Should error when ses.sendTemplatedEmail.promise() fails (6ms)
    
    ----------|---------|----------|---------|---------|-------------------
    File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ----------|---------|----------|---------|---------|-------------------
    All files |     100 |      100 |     100 |     100 |                   
     user.js  |     100 |      100 |     100 |     100 |                   
    ----------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        12.115s