m-radzikowski / aws-sdk-client-mock

AWS JavaScript SDK v3 mocks for easy unit testing. 🖋️ Typed 🔬 Tested 📄 Documented 🛠️ Maintained
https://m-radzikowski.github.io/aws-sdk-client-mock/
MIT License
791 stars 38 forks source link

Unable to mock Secrets Manager with ESM #232

Open jove4015 opened 2 months ago

jove4015 commented 2 months ago

Checklist

Bug description

Using ts-jest with experimental ESM support enabled, I am struggling to mock the SecretsManager. I have been able to get other AWS services to mock properly in this same ecosystem, so I'm not sure what about SecretsManager is different.

(see Example 1 below)

This simple, all in one file test fails:

    expect(received).toBe(expected) // Object.is equality

    Expected: "test_secret"
    Received: undefined

      814 |     console.log(result);
      815 |
    > 816 |     expect(result).toBe("test_secret");
          |                    ^
      817 |   });
      818 | });
      819 |

It detects that the command was called but doesn't get the resolved value it should have.

When I try to use this in a real test, where the credentials are being pulled in a separate class that I'm testing, the test fails and I see the error "CredentialsProviderError: Could not load credentials from any providers":

(see example 2 below)

When you inspect the mock in this scenario, you'll see that the mock never registered any command being run (ie, secretsManagerMock.commandCalls(GetSecretValueCommand).length == 0).

I have also tried moving the mock creation code to a jest setup file as was suggested in this previous issue. However, that didn't change this behavior at all. The mock was not used unless it was declared in the same file with the actual invocation.

Reproduction

// Example 1:

import { mockClient } from "aws-sdk-client-mock";
import {
  SecretsManager,
  GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";

const secretsManagerMock = mockClient(SecretsManager);

secretsManagerMock.on(GetSecretValueCommand).resolves({
  SecretString: "test_secret",
});

  it("Pulls credentials from the secrets manager", async () => {

    const client = new SecretsManager();
    const input = { SecretId: "test_api_key" };
    const result = await client.send(new GetSecretValueCommand(input));
    expect(secretsManagerMock.commandCalls(GetSecretValueCommand)).toHaveLength(
      1,
    );

    expect(result).toBe("test_secret");
  });

// Example 2:

module.test.ts

import { mockClient } from "aws-sdk-client-mock";
import {
  SecretsManager,
  GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";

const secretsManagerMock = mockClient(SecretsManager);

secretsManagerMock.on(GetSecretValueCommand).resolves({
  SecretString: "test_secret",
});

// Import class after mock is created using dynamic top level awaited import

const TestModule = (await import("module.ts")).default;

it("Pulls credentials from the secrets manager", async () => {
  const mod = new TestModule();
  const result = await mod.run();
  expect(result).toBe("test_secret");
});

module.ts

import {
  SecretsManager,
  GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";

export default class TestModule {
   async run() {

    const client = new SecretsManager();
    const input = { SecretId: "test_api_key" };
    const result = await client.send(new GetSecretValueCommand(input));
    return result.SecretString;
   } 
}

Environment

    "@types/jest": "^29.5.5",
    "jest": "^29.7.0",
    "jest-mock-extended": "^3.0.6",
    "ts-jest": "^29.1.2",
    "jest-extended": "^4.0.2",
    "@aws-sdk/client-secrets-manager": "^3.543.0",
    "aws-sdk-client-mock-jest": "^3.0.1",
m-radzikowski commented 1 week ago

Hey, can you provide a small repo with a reproduction? With Jest setup for ESM and exact commands to run to get the error (npm install and npm test in the simplest scenario).