Search code examples
node.jstypescriptunit-testingjestjsgoogle-secret-manager

jest mocking for Google Secret Manager client is broken


i am trying to getting the secrets from GCP Secret Manager as follow:

import { SecretManagerServiceClient } from '@google-cloud/secret-manager';

const getSecrets = async (storeId) => {
  try {
    const client = new SecretManagerServiceClient();
    const [accessReponse] = await client.accessSecretVersion({
      name: `projects/messaging-service-dev-b4f0/secrets/STORE_${storeId}_MESSANGER_CHANNEL_TOKEN/versions/latest`,
    });
    const responsePayload = accessReponse.payload.data.toString();
    return responsePayload;
  } catch (error) {
    console.error(`getSecretInfo: ${error.message}`);
    throw error;
  }
};

export default getSecrets;

for this function, to writing units i need to mock the SecretManagerServiceClient and it's function accessSecretVersion so following up the code i wrote for this.

import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
import { mocked } from 'ts-jest/utils';

import getSecrets from './../get-secrets';

jest.mock('@google-cloud/secret-manager');

const mockAccessSecretVersion = jest.fn().mockReturnValue({
    promise: jest.fn().mockResolvedValue({
        accessReponse: {
            payload: {
                data: 'secret'
            }
        }
    })
})

describe('getSecrets', () => {
    beforeEach(() => {
      jest.clearAllMocks();
      console.error = jest.fn();
    });
  
    it('should console log correct message if token exist on global', async () => {
        const mock = mocked(await (new SecretManagerServiceClient()).accessSecretVersion);
        mock.mockImplementationOnce(() => 'sa') 
        const factory = await getSecrets('1');
        jest.spyOn(global.console, 'error'); 
    });
  
  });
  

here, the test is broken as TypeError: (intermediate value) is not iterable, i am totally blocked here and any leads for this approach to resolve the mocking


Solution

  • This error means you didn't mock the resolved value for the client.accessSecretVersion method correctly. Here is a complete unit testing solution:

    get-secrets.ts:

    import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
    
    const getSecrets = async (storeId) => {
      try {
        const client = new SecretManagerServiceClient();
        const [accessReponse] = await client.accessSecretVersion({
          name: `projects/messaging-service-dev-b4f0/secrets/STORE_${storeId}_MESSANGER_CHANNEL_TOKEN/versions/latest`,
        });
        const responsePayload = accessReponse.payload!.data!.toString();
        return responsePayload;
      } catch (error) {
        console.error(`getSecretInfo: ${error.message}`);
        throw error;
      }
    };
    
    export default getSecrets;
    

    get-secrets.test.ts:

    import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
    import getSecrets from './get-secrets';
    
    const accessReponse = {
      payload: { data: { name: 'teresa teng' } },
    };
    const mClient = {
      accessSecretVersion: jest.fn(),
    };
    
    jest.mock('@google-cloud/secret-manager', () => {
      const mSecretManagerServiceClient = jest.fn(() => mClient);
      return { SecretManagerServiceClient: mSecretManagerServiceClient };
    });
    
    describe('getSecrets', () => {
      beforeEach(() => {
        jest.clearAllMocks();
      });
      afterAll(() => {
        jest.resetAllMocks();
        jest.restoreAllMocks();
      });
    
      it('should console log correct message if token exist on global', async () => {
        mClient.accessSecretVersion.mockResolvedValueOnce([accessReponse]);
        const actual = await getSecrets('1');
        expect(actual).toEqual({ name: 'teresa teng' }.toString());
        expect(SecretManagerServiceClient).toBeCalledTimes(1);
        expect(mClient.accessSecretVersion).toBeCalledWith({
          name: `projects/messaging-service-dev-b4f0/secrets/STORE_1_MESSANGER_CHANNEL_TOKEN/versions/latest`,
        });
      });
    
      it('should log error and rethrow it', async () => {
        const errorLogSpy = jest.spyOn(console, 'error');
        const mError = new Error('network');
        mClient.accessSecretVersion.mockRejectedValueOnce(mError);
        await expect(getSecrets('1')).rejects.toThrowError('network');
        expect(SecretManagerServiceClient).toBeCalledTimes(1);
        expect(mClient.accessSecretVersion).toBeCalledWith({
          name: `projects/messaging-service-dev-b4f0/secrets/STORE_1_MESSANGER_CHANNEL_TOKEN/versions/latest`,
        });
        expect(errorLogSpy).toBeCalledWith('getSecretInfo: network');
      });
    });
    

    unit test result:

     PASS  src/stackoverflow/64857093/get-secrets.test.ts (13.322s)
      getSecrets
        ✓ should console log correct message if token exist on global (6ms)
        ✓ should log error and rethrow it (9ms)
    
      console.error node_modules/jest-environment-jsdom/node_modules/jest-mock/build/index.js:866
        getSecretInfo: network
    
    ----------------|----------|----------|----------|----------|-------------------|
    File            |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    ----------------|----------|----------|----------|----------|-------------------|
    All files       |      100 |      100 |      100 |      100 |                   |
     get-secrets.ts |      100 |      100 |      100 |      100 |                   |
    ----------------|----------|----------|----------|----------|-------------------|
    Test Suites: 1 passed, 1 total
    Tests:       2 passed, 2 total
    Snapshots:   0 total
    Time:        14.744s