middy: Cannot find module '@middy/core' when running jest with typescript and esm

Describe the bug hello I am running typescript with esm and jest and I have a problem with middy:

I had this test:

import { APIGatewayEvent, Context } from 'aws-lambda';
import { lambda } from '../index.js';

describe('get-by-id handler', () => {
    test('Should not find id', async () => {
        const x = jest.fn();
        x();
        expect(x).toHaveBeenCalled();
    });
});

which worked fine with the configurations I supplied in the “To Reproduce section” and npm run test command. howerver when I changed it to look like this:

import { APIGatewayEvent, Context } from 'aws-lambda';
import { lambda } from '../index.js';

describe('get-by-id handler', () => {
    test('Should not find id', async () => {
        const res = await lambda({
            pathParameters: { id: 'id' },
        } as unknown as APIGatewayEvent, {} as Context);
        console.log(res);

        const x = jest.fn();
        x();
        expect(x).toHaveBeenCalled();
    });
});

I get this error

 jest -c jest.config.cjs

 FAIL  app/src/handlers/get-by-id/tests/index.test.ts
  ● Test suite failed to run

    app/src/handlers/get-by-id/index.ts:4:19 - error TS2307: Cannot find module '@middy/core' or its corresponding type declarations.

    4 import middy from '@middy/core';
                        ~~~~~~~~~~~~~

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        11.409 s, estimated 13 s
Ran all test suites.

To Reproduce How to reproduce the behaviour: tsconfig.json:

{
  "extends": "./node_modules/@tsconfig/node20/tsconfig.json",
  "compilerOptions": {
    "declaration": true,
    "outDir": "./dist/",
    "noUnusedParameters": false,
    "noPropertyAccessFromIndexSignature": false,
    "resolveJsonModule": true
  },
}

jest.config.cjs

module.exports = {
    preset: 'ts-jest',
    testEnvironment: 'node',
    testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(ts?)$',
    moduleNameMapper: {
        '^(\\.{1,2}/.*)\\.js$': '$1',
    },
};

package.json:

{
  "type": "module",
  "main": "index.js",
  "scripts": {
    "build": "npm run clean && npm run copy-files && npm run tsc",
    "copy-files": "copyfiles -e \"!{*.md,package?(-lock).json}\" \"**/*.*\" dist",
    "clean": "rimraf ./dist",
    "tsc": "tsc -p tsconfig.build.json",
    "deploy": "cdktf deploy",
    "lint": "npx eslint . --ext .ts,.tsx",
    "test": "jest -c jest.config.cjs",
    "coverage": "jest -c jest-cov.config.cjs"
  },
  "license": "ISC",
  "devDependencies": {
    "@cdktf/provider-aws": "^18.0.6",
    "@tsconfig/node20": "^20.1.2",
    "@types/aws-lambda": "^8.10.129",
    "@types/jest": "^29.5.10",
    "@types/node": "^20.10.0",
    "@typescript-eslint/eslint-plugin": "^6.13.0",
    "@typescript-eslint/parser": "^6.13.0",
    "cdktf": "^0.19.1",
    "constructs": "^10.3.0",
    "copyfiles": "^2.4.1",
    "eslint": "^8.54.0",
    "eslint-config-airbnb-base": "^15.0.0",
    "eslint-config-airbnb-typescript": "^17.1.0",
    "eslint-plugin-import": "^2.29.0",
    "eslint-plugin-require-extensions": "^0.1.3",
    "jest": "^29.7.0",
    "rimraf": "^5.0.5",
    "ts-jest": "^29.1.1",
    "ts-node": "^10.9.1",
    "typescript": "^5.3.2"
  },
  "dependencies": {
    "@aws-sdk/client-dynamodb": "^3.460.0",
    "@aws-sdk/client-secrets-manager": "^3.460.0",
    "@aws-sdk/lib-dynamodb": "^3.460.0",
    "@middy/core": "^5.0.3",
    "axios": "^1.6.2",
    "dotenv": "^16.3.1"
  }
}

handler:

import type { APIGatewayEvent } from 'aws-lambda';
import middy from '@middy/core';
const handler = async (event: APIGatewayEvent) => {
    return {
        statusCode: 200,
    };
};

// eslint-disable-next-line import/prefer-default-export
export const lambda = middy().handler(handler);

Expected behaviour I would expect the test to pass when I run npm run test

Environment (please complete the following information):

  • Node.js: [20.9.0]
  • Middy: [5.0.3]
  • AWS SDK [3.460.0]

Additional context what I have tried so far step by step:

  1. changing test script in packge.json to this “NODE_OPTIONS=–experimental-vm-modules jest -c jest.config.cjs” as mentioned here result was the same

  2. changing jest.config.cjs to this:

module.exports = {
    preset: 'ts-jest/presets/default-esm',
    testEnvironment: 'node',
    testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(ts?)$',
    moduleNameMapper: {
        '^(\\.{1,2}/.*)\\.js$': '$1',
    },
    transform: {
        // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest`
        // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest`
        '^.+\\.tsx?$': [
            'ts-jest',
            {
                useESM: true,
            },
        ],
    },
};

as mentioned here result was the same

  1. changing jest.config.cjs to this:
const esModules = ["@middy"].join("|")
module.exports = {
    preset: 'ts-jest/presets/default-esm',
    testEnvironment: 'node',
    testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(ts?)$',
    moduleNameMapper: {
        '^(\\.{1,2}/.*)\\.js$': '$1',
    },
    transform: {
        '^.+\.ts?$': [
            'ts-jest',
            {
                useESM: true,
            },
        ],
    },
    transformIgnorePatterns: [`node_modules/(?!${esModules})`],
};

as mentioned here result was the same

  1. changing tsconfig.json to this:
{
  "compilerOptions": {
    "incremental": true,
    "target": "es2020",
    "module": "es2020",
    "declaration": true,
    "sourceMap": true,
    "composite": true,
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "preserveConstEnums": true,
    "resolveJsonModule": true,
    "allowJs": true,
    "rootDir": ".",
    "outDir": "lib"
  },
  "include": ["src/**/*", "tests/**/*"],
  "exclude": ["node_modules"]
}

as mentioned here got this error:

ReferenceError: jest is not defined

       9 |         console.log(res);
      10 |
    > 11 |         const x = jest.fn();
         |                   ^
      12 |         x();
      13 |         expect(x).toHaveBeenCalled();
      14 |     });

      at Object.<anonymous> (app/src/handlers/get-by-id/tests/index.test.ts:11:19)

About this issue

  • Original URL
  • State: closed
  • Created 7 months ago
  • Reactions: 19
  • Comments: 15 (4 by maintainers)

Most upvoted comments

**I am experiencing the same issue! ⬆️ 😞 **

When running:

npm run test 

Where:

  "scripts": {
    [...]
    "test": "NODE_OPTIONS=--experimental-vm-modules jest --no-cache",
  },

Dependencies:

  • "@middy/core": "^5.1.0"
  • "typescript": "^5.2.2"
  • "ts-jest": "^29.1.1"
  • "ts-node": "^10.9.1"
  • "jest": "^29.7.0",
  • "@types/jest": "^29.5.7",
  • "ts-jest": "^29.1.1",

My jest.config.ts:

import { JestConfigWithTsJest } from 'ts-jest';

const esModules = ['@middy'].join('|');

const config: JestConfigWithTsJest = {
  verbose: true,
  moduleFileExtensions: ['js', 'json', 'ts'],
  rootDir: 'src',
  testRegex: '.*\\.spec\\.ts$',
  collectCoverageFrom: ['**/*.(t|j)s'],
  transform: { '^.+\\.(t|j)s$': ['ts-jest', { useESM: true }] },
  transformIgnorePatterns: [`node_modules/(?!${esModules})`],
  testEnvironment: 'node',
  moduleNameMapper: { '^~/(.+)$': '<rootDir>/$1' },
  moduleDirectories: ['node_modules', 'src'],
};

export default config;

My tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ES2022",
    "lib": ["ES2022"],
    "allowJs": true,
    "isolatedModules": true,
    "strict": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "baseUrl": "./",
    "paths": { "~/*": ["./src/*"] },
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "typeRoots": ["node_modules/@types", "./src/shared/types"]
  },
  "include": ["src", "./.eslintrc.js", "./jest.config.ts"],
  "exclude": ["node_modules", "dist"]
}

Notes:

I’ve spent days fighting this thinking it was my setup. This is really critical

Please try this, it works for my case.

In the jest.config.js:

transformIgnorePatterns: ['/node_modules/(?!(@middy/core)/)'],
moduleNameMapper: {
   '^@middy/core$': '<rootDir>/node_modules/@middy/core',
}

The docs as described in the middy files didn’t quite help me.

The biggest impact was NODE_OPTIONS=–experimental-vm-modules, although this causes another set of issues.

The simplest middy + jest setup I could create https://github.com/jrobens/middyskel. Not very ‘deterministic’ as there are a lot of moving parts.