Pmits
Pmits

Reputation: 315

Jest - Mock promisified SQS calls

Let's assume that I have a QueueClass with a method send, that gets some data as parameter which then sends to a SQS queue.
I want to write 2 tests:
One that tests that the MessageBody and QueueUrl keys have the expected values passed in.
One that in case of error an exception gets thrown.

The method to be tested looks like this:
My send method:

async send(data) {
  return SQS.sendMessage({
    MessageBody: JSON.stringify(data),
    QueueUrl: 'queue_url_here',
  })
    .promise()
    .catch((error) => {
      // Throw exception ...
    });
}

The test I have for that method:

const aws = require('aws-sdk');

jest.mock('aws-sdk', () => {
  const SQSMocked = {
    sendMessage: jest.fn().mockReturnThis(),
    promise: jest.fn(),
  };

  return {
    SQS: jest.fn(() => SQSMocked),
  };
});

sqs = new aws.SQS();

test('my test', async () => {
  const data = {};
  await QueueClass.send(data);

  expect(sqs.sendMessage).toHaveBeenCalledWith({
    MessageBody: JSON.stringify(data),
    QueueUrl: 'queue_url_here',
  });
});

That test gives me the following error:
TypeError: Cannot read property 'catch' of undefined
I did try adding catch: jest.fn() to the SQSMocked object, the exact same way I do with promise, but kept getting the same error.

The thing is that when I change the method that I am trying to test so it uses try-catch block instead of .promise() and .catch() :

async send(data) {
  try {
    return SQS.sendMessage({
      MessageBody: JSON.stringify(data),
      QueueUrl: 'queue_url_here',
    });
  } catch (error) {
    // Throw exception ...
  }
}

my test passes, so that makes me think that this is not necessarily an issue about properly mocking the SQS.

Any ideas why when using .promise() and .catch() my test fails ?
Also how could I test a case where an Error gets thrown by the queue ?
I would like to be able to do something like this:

  await expect(sqs.sendMessage)
    .resolves
    .toEqual(...);

OR

  await expect(sqs.sendMessage)
    .rejects
    .toThrowError(new Error('Some error thrown.'));

Upvotes: 1

Views: 4469

Answers (2)

jen
jen

Reputation: 349

duplicate questions How to mock AWS sqs call for unit testing

just add the method around to be called like so...

class EventService {
  static async sendFifoMessage(
    url,
    message,
    groupId,
    dedupeId,
  ) {
    const sqsMessageRequest = {
      QueueUrl: url,
      MessageBody: JSON.stringify(message),
      MessageGroupId: groupId,
    };
    if (!!dedupeId) {
      sqsMessageRequest.MessageDeduplicationId = dedupeId;
    }
    return await new SQS().sendMessage(sqsMessageRequest).promise();
  }
}

import AWS = require('aws-sdk');

const URL = 'URL';
const MESSAGE = 'MESSAGE';
const GROUP_ID = 'GROUP_ID';
const DEDUPE_ID = 'DEDUPE_ID';
const BAD_REQUEST = 'BAD_REQUEST';
jest.mock('aws-sdk', () => {
  const SQSMocked = {
    sendMessage: jest.fn().mockReturnThis(),
    promise: jest.fn(),
  };
  return {
    SQS: jest.fn(() => SQSMocked),
  };
});

const sqs = new AWS.SQS({
  region: 'us-east-1',
});

describe('EventService', () => {
  beforeEach(() => {
    (sqs.sendMessage().promise as jest.MockedFunction < any > ).mockReset();
  });

  afterAll(() => {
    jest.clearAllMocks();
    jest.restoreAllMocks();
    jest.resetAllMocks();
  });

  describe('sendFifoMessage', () => {
    const messageResult = {
      QueueUrl: URL,
      MessageBody: JSON.stringify(MESSAGE),
      MessageGroupId: GROUP_ID,
      MessageDeduplicationId: DEDUPE_ID,
    };
    it('sendMessage successfully', async() => {
      (sqs.sendMessage().promise as jest.MockedFunction < any > ).mockResolvedValueOnce('mocked data');
      await EventService.sendFifoMessage(URL, MESSAGE, GROUP_ID, DEDUPE_ID);
      expect.assertions(2);
      expect(sqs.sendMessage).toBeCalledWith(messageResult);
      expect(sqs.sendMessage().promise).toBeCalledTimes(1);
    });

    it('sendMessage throws', async() => {
      (sqs.sendMessage().promise as jest.MockedFunction < any > ).mockRejectedValueOnce(BAD_REQUEST);
      expect(async() => await EventService.sendFifoMessage(URL, MESSAGE, GROUP_ID, DEDUPE_ID)).rejects.toThrowError(
        new Error(BAD_REQUEST),
      );
      expect(sqs.sendMessage).toBeCalledWith(messageResult);
      expect(sqs.sendMessage().promise).toBeCalledTimes(1);
    });
  });
});

Upvotes: 0

Estus Flask
Estus Flask

Reputation: 222503

promise is stubbed and returns undefined, this is the reason why it doesn't return a promise that could be chained. It's supposed to return a promise, as the name suggests.

Since values may be different in different tests, it's better to expose it as a variable. sendMessage can be exposed as well for assertions:

const mockPromiseFn = jest.fn();
const mockSendMessage = jest.fn().mockReturnThis();

jest.mock('aws-sdk', () => {
  return {
    SQS: jest.fn().mockReturnValue({
      sendMessage: mockSendMessage,
      promise: mockPromiseFn
    })
  };
});

It doesn't make sense to test it with await expect(sqs.sendMessage).rejects... because it tests the code you've just written.

It likely should be:

mockPromiseFn.mockRejectedValue(new Error(...));
await expect(QueueClass.send(data)).rejects.toThrowError(...);
expect(mockSendMessage).toBeCalledWith(...);

This is potentially a mistake:

async send(data) {
  try {
    return SQS.sendMessage(...);
  } catch (error) {
    // Throw exception ...
  }
}

try..catch is unable to catch asynchronous errors from async return, also sendMessage return value wasn't converted to a promise.

It should be:

async send(data) {
  try {
    return await SQS.sendMessage(...).promise();
  } catch (error) {
    // Throw exception ...
  }
}

Upvotes: 1

Related Questions