How to mock @google-cloud/kms using jest

774 Views Asked by At

I'm trying to write unit test cases for decrypt. I've my own implementation of decrypting an encrypted file. While trying to import the decrypt.mjs facing the following error.

Must use import to load ES Module: /node_modules/bignumber.js/bignumber.mjs

My application is a react frontend and NodeJS backend. I've used ES6 modules for NodeJS. Here is my decrypt.mjs file

import { readFile } from 'fs/promises';
import path from 'path';

import { KeyManagementServiceClient } from '@google-cloud/kms';

const decrypt = async (APP_MODE, __dirname) => {
  if (APP_MODE === 'LOCALHOST') {
    const keys = await readFile(
      new URL(`./stagingfile.json`, import.meta.url)
    ).then((data) => JSON.parse(data));
    return keys;
  }
  const { projectId, locationId, keyRingId, cryptoKeyId, fileName } =
    getKMSDefaults(APP_MODE);
  const ciphertext = await readFile(
    path.join(__dirname, `/${fileName}`)
  );
  const formattedName = client.cryptoKeyPath(
    projectId,
    locationId,
    keyRingId,
    cryptoKeyId
  );
  const request = {
    name: formattedName,
    ciphertext,
  };
  const client = new KeyManagementServiceClient();
  const [result] = await client.decrypt(request);
  return JSON.parse(result.plaintext.toString('utf8'));
};

const getKMSDefaults = (APP_MODE) => {
//Based on APP_MODE the following object contains different values
  return {
    projectId: PROJECT_ID,
    locationId: LOCATION_ID,
    keyRingId: KEY_RING_ID,
    cryptoKeyId: CRYPTO_KEY_ID,
    fileName: FILE_NAME,
  };
};

export default decrypt;

I tried to mock the @google-cloud/kms using manual mock (jest) but it didn't work. I tried multiple solutions to mock but nothing worked and it ended with the Must use import to load ES Module error.

1

There are 1 best solutions below

0
On

I've had successfully used jest to mock @google-cloud/kms with TypeScript, so hopefully this will be the same process for ES modules that you can use.

Example working code:

// jest will "hoist" jest.mock to top of the file on its own anyway
jest.mock("@google-cloud/kms", () => {
  return {
    KeyManagementServiceClient: jest.fn().mockImplementation(() => {
      return {
        encrypt: kmsEncryptMock,
        decrypt: kmsDecryptMock,
        cryptoKeyPath: () => kmsKeyPath,
      };
    }),
  };
});

// give names to mocked functions for easier access in tests
const kmsEncryptMock = jest.fn();
const kmsDecryptMock = jest.fn();

const kmsKeyPath = `project/location/keyring/keyname`;

// import of SUT must be after the variables used in jest.mock() are defined, not before.
import { encrypt } from "../../src/crypto/google-kms";

describe("Google KMS encryption service wrapper", () => {
  const plaintext = "some text to encrypt";
  const plaintextCrc32 = 1897295827;

  it("sends the correct request to kms service and raise error on empty response", async () => {
    // encrypt function is async that throws a "new Error(...)"
    await expect(encrypt(plaintext)).rejects.toMatchObject({
      message: "Encrypt: no response from KMS",
    });
    expect(kmsEncryptMock).toHaveBeenNthCalledWith(1, {
      name: kmsKeyPath,
      plaintext: Buffer.from(plaintext),
      plaintextCrc32c: { value: plaintextCrc32 },
    });
  });

});