Home > Net >  Jest with TypeScript (Backend - Node/Express) says Cannot use import statement outside a module
Jest with TypeScript (Backend - Node/Express) says Cannot use import statement outside a module

Time:04-23

I am working on Jest tests for a Node/Express TypeScript backend. I recently learnt about global test setup which I am implementing to reduce similar variables and function calls that are executed in all test files.
So for example in the below code I want to make the app and userToken global variables and await initDatabase() function called in the tests setup file.⬇

import initServer from '../initServer';
import { initDatabase } from '../util/databaseHandler';
import loginWithAWS from '../util/loginWithAWS';

const app = initServer();
let userToken: any;

describe(`Test on the GET method of route `, () => {

  beforeAll( async() => {
    try{
      await initDatabase();
      userToken = await loginWithAWS('userRole');
    }catch(error){
      return error;
    }
  });
});

However, after implementing the global test setup with the variables and function then running the test on terminal/bash/cmd via the command yarn test this error is thrown: ⬇
error thrown

The solution in progress which throws the bug is implemented as shown below. Your assistance will be highly valued.:
The test setup file:
testSetup.ts

import initServer from '../initServer';
import { initDatabase } from '../util/databaseHandler';
import loginWithAWS from '../util/loginWithAWS';


const initializeTestingSetup = async function() {
  (global as any).app = initServer();
  await initDatabase();
  (global as any).userToken = await loginWithAWS('userRole');
};

module.exports = initializeTestingSetup;

Below is my jest config file for the project's tests:
jest.config.js

/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
  transform: {
    '^. \\.ts$': 'ts-jest',
    '^. \\.js$': 'babel-jest',
    '^. \\.mjs$': 'babel-jest',
  },
  moduleDirectories: ['node_modules', '<rootDir>/src'],
  moduleNameMapper: {
    '@controllers/(.*)': '<rootDir>/src/controllers/$1',
    '@middleware/(.*)': '<rootDir>/src/middleware/$1',
    '@models/(.*)': '<rootDir>/src/models/$1',
    '@routes/(.*)': '<rootDir>/src/routes/$1',
    '@types/(.*)': '<rootDir>/src/types/$1',
    '@util/(.*)': '<rootDir>/src/util/$1',
  },
  globals: {
    'ts-jest' : {
      astTransformers: {
        before: [
          'ts-jest/dist/transformers/path-mapping'
        ]
      },
    } 
  },
  globalSetup: './src/__tests__/testSetup.ts',
};

I would really appreciate your help.

CodePudding user response:

Jest seems to have problems with ES6 import syntax in any globalSetup scripts. I would try to change your testSetup.ts script to use require instead of import so it doesn't have to be an ES6 module. Note that you are mixing CommonJS and ES6/ESM syntax in your testSetup.js because you have import, but use module.exports. Something like this might work perhaps (this is untested):

const initServer = require('../initServer');
const initDatabase = require('../util/databaseHandler');
const loginWithAWS = require('../util/loginWithAWS');


const initializeTestingSetup = async function() {
  (global as any).app = initServer();
  await initDatabase();
  (global as any).userToken = await loginWithAWS('userRole');
};

module.exports = initializeTestingSetup;

UPDATE: What about trying to make the globalsetup more compatible with ESM:

import initServer from '../initServer';
import { initDatabase } from '../util/databaseHandler';
import loginWithAWS from '../util/loginWithAWS';

export default async function setup() {
  (global as any).app = initServer();
  await initDatabase();
  (global as any).userToken = await loginWithAWS('userRole');
};

Does that help?

CodePudding user response:

Ensure that package.json has the type: module set. I'm using node14, so I've set my target and lib to have es2020 as suggested here

The tsconfig for tests:

{
  "compilerOptions": {
    "allowUnreachableCode": false,
    "declaration": true,
    "target": "es2020",
    "lib": ["es2020"],
    "importHelpers": true,
    "incremental": true,
    "module": "es2020",
    "esModuleInterop": true,
    "moduleResolution": "Node",
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": true,
    "noImplicitOverride": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "resolveJsonModule": true,
    "sourceMap": true,
    "target": "es2020"
  },
  "include": ["test"],
  "exclude": ["build", "node_modules"]
}

I've set esModuleInterop to avoid any issues that might occur with default imports and set module to es2020 (which allows for import.meta for __dirname).

We can ask the jest config to treat modules as esm:

{
  "preset": "ts-jest",
  "testEnvironment": "node",
  "testMatch": ["<rootDir>/test/**/*.test.ts"],
  "rootDir": ".",
  "extensionsToTreatAsEsm": [".ts"],
  "globalSetup": "<rootDir>/test/setup.ts",
  "globals": {
    "ts-jest": "./tsconfig.tests.json"
  }
}

In your global setup, stick to esm exports and imports:

import express from "express";
import bodyParser from "body-parser";
import morgan from "morgan";
import helmet from "helmet";
import cors from "cors";
import PicoDb from "picodb";

const app = express();

const logger = morgan("combined");

const jsonParser = bodyParser.json();
const queryParser = bodyParser.urlencoded({ extended: true });

app.use(logger);
app.use(helmet());

app.use(queryParser);
app.use(jsonParser);

app.use(cors());
app.options("*", cors());

export { app };

export default async function globalSetup() {
  const db = new PicoDb();
  await db.insertMany([{ a: 1 }, { a: 2, b: 2 }]);
  app.listen(3000, () => {
    console.log("Server listening on port 3000");
  });
}

This worked as expected when I was running the tests (with jest not exiting because I did not close the server in a teardown step).

  • Related