Unable to mock ESM with unstable_mockModule

In my project, I am utilizing ESM. The transpiled .ts files are converted into .js and stored in the 'dist' directory. Here is an example of how my directory structure looks:

dist/
├─ src/
│  ├─ store/
│  │  ├─ index.js
│  ├─ strategies/
│  │  ├─ scalping/
│  │  │  ├─ main.js 
src/
├─ store/
│  │  ├─ index.ts
├─ strategies/
│  │  ├─ scalping/
│  │  │  ├─ main.ts 
test/
├─ strategies/
│  │  ├─ scalping/
│  │  │  ├─ main.test.ts 

The moduleResolution specified in my tsconfig.json file is "NodeNext"

Here is a snippet from my jest.config.ts:

import type { Config } from "jest"

const config: Config = {
    preset: 'ts-jest/presets/default-esm',
    displayName: "scalper",
    testTimeout: 60000,
    globalSetup: "<rootDir>/test/jest/setup.ts",
    globalTeardown: "<rootDir>/test/jest/teardown.ts",
    coveragePathIgnorePatterns: [ "/node_modules/" ],
    testMatch: ["<rootDir>/test/**/?(*.)+(spec|test).[jt]s?(x)"],
    modulePathIgnorePatterns: ["<rootDir>/test/.*/__mocks__", "<rootDir>/dist/"],
    testEnvironment: "node",
    coverageDirectory: "<rootDir>/coverage",
    extensionsToTreatAsEsm: [".ts"],
    moduleNameMapper: {
        "^(.*)\\.js$": "$1",
    },
    transform: {
        '^.+\\.ts?$': [
            'ts-jest',
            {
                useESM: true
            }
        ]
    },
}

export default config

The content of my test/main/index.test.ts is as follows:

import { jest } from '@jest/globals'
import * as fs from "fs"
import * as path from "path"
import appRoot from "app-root-path"

const params = JSON.parse(fs.readFileSync(path.join(appRoot.path, 'test/strategies/scalping/main/params.json'), 'utf8'))

import * as strategies from "../../../../src/strategies/scalping/index.js"

describe('main', () => {
    it('Should create a valid scalp trade', async () => {
        const { builtMarket, config } = params

        // Mocking of ESM requires dynamic imports
        jest.unstable_mockModule('../../../../src/store/index.js', () => ({
            getScalpRunners: jest.fn().mockResolvedValue(params as never)
        }))

        const store = await import('../../../../src/store/index.js')

        await strategies.scalping(builtMarket, config)
    })
})

I expected the method getScalpRunners to be mocked properly, but it's not working as intended and calls the main definition instead. I have referred to the documentation on mocking ESM modules as well as some external resources but haven't resolved this issue yet.

An excerpt from my src/strategies/scalping/main.ts is included below:

import store from "../../store/index.js"

export default async function(builtMarket: BuiltMarket, config: Config): Promise<BuiltMarket | undefined> {
    try {
        const validMarket = isValidMarket(builtMarket, config)

        if (validMarket) {
            const polledRunners: ScalpRunner[] | undefined = await store.getScalpRunners({
                eventId: builtMarket._eventId,
                marketId: builtMarket._marketId
            })

            // other functionality...
        }
    } catch(err: any) {
        throw err
    }
}

Despite the fact that store appears to be correctly mocked in index.test.ts, it reverts back to being the un-mocked version in main.ts.

Answer №1

The issue has been resolved, but the solution appears cluttered. I modified the import statement for "../../../../src/strategies/scalping/index.js" to a dynamic import rather than a static one in order to ensure that it was using the mocked version. Additionally, I had to mock a default export for "store/index.js".

Within "index.test.ts":

// Importing global is not usually necessary, but it seems ESM requires it
// Reference: https://jestjs.io/docs/ecmascript-modules#differences-between-esm-and-commonjs
import { jest } from '@jest/globals'

// Mocking ESM requires dynamic imports
// Reference: https://jestjs.io/docs/ecmascript-modules#module-mocking-in-esm
jest.unstable_mockModule('../../../../src/store/index.js', () => ({
    default: {
        getScalpRunners: jest.fn().mockResolvedValue(params as never)
    }
}))

import * as fs from "fs"
import * as path from "path"
import appRoot from "app-root-path"

const params = JSON.parse(fs.readFileSync(path.join(appRoot.path, 'test/strategies/scalping/main/params.json'), 'utf8'))

// Mocking ESM requires dynamic imports
// This ensures that the import is done AFTER the mock
const store = await import('../../../../src/store/index.js')
const strategies = await import('../../../../src/strategies/scalping/index.js')

describe('main', () => {
    it('Should create a valid scalp trade', async () => {
        const { builtMarket, config } = params

        await strategies.scalping(builtMarket, config)
    })
})

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When incorporating a JS React component in TypeScript, an error may occur stating that the JSX element type 'MyComponent' is not a valid constructor function for JSX elements

Currently, I am dealing with a JavaScript legacy project that utilizes the React framework. Within this project, there are React components defined which I wish to reuse in a completely different TypeScript React project. The JavaScript React component is ...

How can I utilize Angular and TypeScript to loop through the innerHTML property binding effectively?

I'm currently working on using ngFor to display the binding details of the innerHtml property. <ul> <li *ngFor="let n of NotificationData"> {{n.NotificationID}} <label [innerHtml]="n.NotificationText | StyleHtml&quo ...

I'm having trouble configuring the header in my Node/Express route

Using Node and the Express framework for my backend, along with React for my frontend, all coded in Typescript. The elastic search client is responsible for fetching data on the backend, but I don't believe that's where the issue lies. I'm ...

When declaring an array of numbers in sequelize-typescript, it triggers a TypeScript error

In my application, I am working with PostgreSQL, Sequelize, Sequelize-TypeScript, and TypeScript. I have a need for a table called Role where each role has multiple permissions of type integer. I'm following the guidelines provided in the sequelize-ty ...

The modal stubbornly refuses to close

The main component responsible for initiating the process is /new-order. Upon clicking on the confirm button, a modal window appears. <div class="col-12"> <button type="button" class="btn btn-primary m-1" (click)=& ...

Tips for effectively generating a JSON object array in Typescript

Currently, I'm attempting to construct an array of JSON objects using TypeScript. Here is my current method: const queryMutations: any = _.uniq(_.map(mutationData.result, function (mutation: Mutation) { if (mutation && mutation.gene) { co ...

the behavior subject remains static and does not update

Working on setting my language in the BehaviorSubject with a default value using a LanguageService. The service structure is as follows import {Injectable} from '@angular/core'; import * as globals from '../../../environments/globals'; ...

Unpacking objects in Typescript

I am facing an issue with the following code. I'm not sure what is causing the error or how to fix it. The specific error message is: Type 'CookieSessionObject | null | undefined' is not assignable to type '{ token: string; refreshToken ...

Frozen objects in Typescript 2 behave in a variety of ways depending on their shape

Let's say I'm working with an object whose inner structure is unknown to me because I didn't create it. For instance, I have a reference to an object called attributes that contains HTML attributes. I then made a shallow copy of it and froze ...

A method for modifying the key within a nested array object and then outputting the updated array object

Suppose I have an array called arr1 and an object named arr2 containing a nested array called config. If the key in the object from arr1 matches with an id within the nested config and further within the questions array, then replace that key (in the arr1 ...

Creating a type definition for the createSelector function based on the useQuery result

Struggling to find the correct typings for the createSelector res parameter from redux-js, especially in TypeScript where there are no examples or explanations available. The only guidance is provided in JS. const selectFacts = React.useMemo(() => { ...

The reCAPTCHA feature in Next.js form is returning an undefined window error, possibly due to an issue with

Trying to incorporate reCAPTCHA using react-hook-form along with react-hook-recaptcha is posing some challenges as an error related to 'window' being undefined keeps popping up: ReferenceError: window is not defined > 33 | const { recaptchaL ...

Typescript: issue with type guard functionality not functioning properly

type VEvent = { type: "VEVENT"; summary: string; }; type VCalendar = { type: "VCALENDAR"; }; const data: Record<string, VEvent | VCalendar> = (...); array.map((id) => { return { id, title: dat ...

No slides will be displayed in Ionic 2 once the workout is completed

Here is the result of the JSONOBJ: In my home.html file, I have ion-card containing a method called navigate(), which is structured as follows: navigate(event, exercise, exercise2, exercise3, exercise4){ this.navCtrl.push(exerciseSlides, { ...

Testing chai: verifying the inclusion of object types in an array

I am currently in the process of testing a Node.js/Typescript application. My goal is to have my function return an array consisting of objects. These objects should adhere to the following type: type myType = { title: string; description: string; ...

Support for BigInt is not available in TypeScript version 3.5.*

It seems that TypeScript has supported BigInt since version 3.2, and my project is using TypeScript 3.5. Despite not explicitly declaring any variables as BigInt, I recently integrated a package called BufferUtility from https://github.com/Pharuxtan/Buffer ...

Angular Form Validation: Ensuring Data Accuracy

Utilizing angular reactive form to create distance input fields with two boxes labeled as From and To. HTML: <form [formGroup]="form"> <button (click)="addRow()">Add</button> <div formArrayName="distance"> <div *n ...

How do @material-ui/core and @types/material-ui interact with each other?

Exploring a sample project that utilizes material-ui. Upon inspecting the package.json file, I noticed the inclusion of the following packages: { ... "dependencies": { "@material-ui/core": "^1.4.1", ... }, "devDependencies": { "@types ...

Guide on utilizing async/await in .ts files (Encountering error message: "async functions can only be used when targeting ECMAScript 6 or above")

Initially, my project consisted of only app.js using ExpressJS as the main file with numerous lines of code. My development manager instructed me to refactor the code and move some functions into a separate .ts file (transition from JavaScript to TypeScrip ...

Having trouble linking the date object with the default value of the date input field

Exploring how to set the default value of a date type input using property binding. Initially, I attempted to create a new date object in app.component.ts and then bind the [value] attribute of the date input to the currentDate property within app.compone ...