Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to mock "Top-Level" awaits AWS sdk calls #78

Closed
jmandawg opened this issue Feb 11, 2022 · 2 comments
Closed

Unable to mock "Top-Level" awaits AWS sdk calls #78

jmandawg opened this issue Feb 11, 2022 · 2 comments

Comments

@jmandawg
Copy link

jmandawg commented Feb 11, 2022

Amazon now recommends using top-level awaits for performance in lambdas.
https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/

Unfortunately mocks do not work on "top-level awaits.

Example code from the amazon page linked above:

// method2 – ES module

// ES module import syntax
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm"; 

const ssmClient = new SSMClient();
const input = { "Name": "/configItem" }
const command = new GetParameterCommand(input);
const parameter = await ssmClient.send(command); // top-level await

export async function handler() {
    const response = {
        statusCode: 200,
        "body": parameter.Parameter.Value
    };
    return response;
};

Simple test:

import {mockClient} from 'aws-sdk-client-mock';
import { SSMClient, GetParametersByPathCommand } from '@aws-sdk/client-ssm';

const snsMock = mockClient(SSMClient);
snsMock.resolves({value: "hello"});

import {handler} from '../index.js'

const result = await handler();
console.log(result);

result:
CredentialsProviderError: Could not load credentials from any providers

@m-radzikowski
Copy link
Owner

In ES modules all imports are done at the beginning, no matter where they are declared.

issue78.ts:

console.log('handler file')

export const handler = async () => {
    return '';
};

issue78.test.ts:

console.log('before import handler');
import {handler} from '../src/issue78'
console.log('after import handler');

Prints:

handler file
before import handler
after import handler

This is in contrast to CommonJS, where require() is executed at the place in code where it's called.

So in fact the top-level await is called before we setup a mock in our test.

A workaround is to setup the mock before our test file. In jest, you can do this with setupFilesAfterEnv configuration option. It would be like this:

package.json:

{
  ...
  "jest": {
    "testEnvironment": "node",
    "setupFilesAfterEnv": [
      "./test/setup.js"
    ]
  }

}

test/setup.js:

import {mockClient} from 'aws-sdk-client-mock';
import {GetParameterCommand, SSMClient} from '@aws-sdk/client-ssm';

const snsMock = mockClient(SSMClient);
snsMock.on(GetParameterCommand).resolves({
    Parameter: {Value: 'qq'}
});

I tested it and it works. The drawback is that you need to set it in a global setup file, and cannot change between test invocations. However, I don't see any other option, as those are the ES modules' constraints.

I also did not find many examples of unit testing top-level await online. I'm open to any suggestions.

I'm closing this since it's not a problem with the lib itself, and the solution above works.

@noseworthy
Copy link

I was able to get this working by using vitests vi.hoisted() function. It'll hoist the mock creation above all the imports and test setup so that the mock is available by the time your test module is loaded:

import { vi } from 'vitest';

const mockSecretsManager = await vi.hoisted(async () => {
  const { mockClient } = await import('aws-sdk-client-mock');
  const { SecretsManager } = await import('@aws-sdk/client-secrets-manager');

  return mockClient(SecretsManager);
});

Hopefully this helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants