Search code examples
unit-testingtypesjestjsts-jestaws-sdk-mock

How to mock typescript services and functions that use AWS resources?


I am having a Typescript backend structure and I want to create unit tests for all the functionalities. I am using JEST and aws-skd-mock for mocking AWS. I have tried some things but it seems I am not doing the right thing.

I have this service where I am getting a parameter from ParamterStore (amazon.service.ts):

import * as AWS from "aws-sdk";

export class AmazonService {

  parameterStore: AWS.SSM;

  constructor() {
    this.parameterStore = new AWS.SSM();
  }

  async getParam(param) {
    let self = this;
    console.log('IN getPARAM', param);
    return new Promise(function(resolve, reject){
      self.parameterStore.getParameter({
        Name: param,
        WithDecryption: true
      }, function (err, data) {
        if (err) {
          console.log('Error ', err);
          return resolve({Error: 'ParameterNotFound'})
        }
        console.log('RES ', data.Parameter.Value);
        return resolve(data.Parameter.Value)
      })
    })
  }

}

Then, I mock whole amazon.service file, I mock SSM.getParameter with response in my test file (amazon.service.spect.ts):

import * as AWSMock from "aws-sdk-mock";
import * as AWS from "aws-sdk";
import {AmazonService} from "./amazon.service";


jest.mock('./amazon.service');

describe('amazon service mock', () => {
  let amazonService: AmazonService;

  it('should get Parameter from Parameter Store', async () => {
    const ssmGetParameterPromise = jest.fn().mockReturnValue({
      promise: jest.fn().mockResolvedValue({
        Parameter: {
          Name: 'NAME',
          Type: 'SecureString',
          Value: 'VALUE',
          Version: 1,
          LastModifiedDate: 1546551668.495,
          ARN: 'arn:aws:ssm:eu-test-1:123:NAME'
        }
      })
    });
    AWSMock.setSDKInstance(AWS);
    AWSMock.mock('SSM', 'GetParameter', ssmGetParameterPromise);
    amazonService = new AmazonService();

    console.log(await amazonService.getParam('NAME'))

    await expect(amazonService.getParam('NAME')).resolves.toBe('VALUE')
  })
});

With this I get undefined when amazonService.getParam is called.

As I looked in the examples they are initializing new AWS.SSM() right after is mocked and call it from test, but I want to achieve that by calling my function. It seems like SSM is not mocked when my function is called.

Any suggestions how to do this right ?


Solution

  • You don't need to mock ./amazon.service.ts module. Here is the unit test solution without using aws-sdk-mock.

    E.g.

    amazon.service.ts:

    import * as AWS from 'aws-sdk';
    
    export class AmazonService {
      parameterStore: AWS.SSM;
    
      constructor() {
        this.parameterStore = new AWS.SSM();
      }
    
      async getParam(param) {
        let self = this;
        console.log('IN getPARAM', param);
        return new Promise(function (resolve, reject) {
          self.parameterStore.getParameter(
            {
              Name: param,
              WithDecryption: true,
            },
            function (err, data) {
              if (err) {
                console.log('Error ', err);
                return resolve({ Error: 'ParameterNotFound' });
              }
              console.log('RES ', data.Parameter!.Value);
              return resolve(data.Parameter!.Value);
            },
          );
        });
      }
    }
    

    amazon.service.spec.ts:

    import * as AWS from 'aws-sdk';
    import { AmazonService } from './amazon.service';
    import { mocked } from 'ts-jest/utils';
    import { AWSError } from 'aws-sdk';
    import { GetParameterResult } from 'aws-sdk/clients/ssm';
    
    jest.mock('aws-sdk', () => {
      const mSSMInstance = {
        getParameter: jest.fn(),
      };
      const mSSM = jest.fn(() => mSSMInstance);
    
      return { SSM: mSSM };
    });
    
    describe('amazon service mock', () => {
      let amazonService: AmazonService;
    
      it('should get Parameter from Parameter Store', async () => {
        amazonService = new AmazonService();
        expect(AWS.SSM).toBeCalled();
        const mSSMInstance = new AWS.SSM();
        const mData = {
          Parameter: {
            Name: 'NAME',
            Type: 'SecureString',
            Value: 'VALUE',
            Version: 1,
            LastModifiedDate: new Date(1995, 11, 17),
            ARN: 'arn:aws:ssm:eu-test-1:123:NAME',
          },
        };
        mocked(mSSMInstance.getParameter).mockImplementationOnce(
          (params, callback?: (err: AWSError | null, data: GetParameterResult) => void): any => {
            if (callback) {
              callback(null, mData);
            }
          },
        );
        const actual = await amazonService.getParam('NAME');
        expect(actual).toBe('VALUE');
      });
    });
    

    unit test result with coverage report:

     PASS  stackoverflow/61871955/amazon.service.spec.ts (9.613s)
      amazon service mock
        ✓ should get Parameter from Parameter Store (19ms)
    
      console.log
        IN getPARAM NAME
    
          at AmazonService.<anonymous> (stackoverflow/61871955/amazon.service.ts:12:13)
    
      console.log
        RES  VALUE
    
          at stackoverflow/61871955/amazon.service.ts:24:19
    
    -------------------|---------|----------|---------|---------|-------------------
    File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    -------------------|---------|----------|---------|---------|-------------------
    All files          |   86.67 |       50 |     100 |   85.71 |                   
     amazon.service.ts |   86.67 |       50 |     100 |   85.71 | 21-22             
    -------------------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        10.925s