node + ts-jest + ESM, jest.mock doing nothing - node.js

I am using ts-jest with ESM imports on nodejs.
The problem is that my jest.mock is not working, it is not mocking.
import { jest } from "#jest/globals";
jest.mock("./helpers"); // I tried before or after import
import { fn } from "./helpers";
describe("SimpleTest", () => {
it("should work", () => {
console.log(fn); // => defined
console.log(fn.mockReturnValue); // => /!\ UNDEFINED, fn is not a jest mock.
});
});
My jest config:
export default {
preset: "ts-jest/presets/default-esm",
extensionsToTreatAsEsm: [".ts"],
globals: {
"ts-jest": {
useESM: true,
},
},
}
The command I use:
node --experimental-vm-modules --experimental-specifier-resolution=node $(yarn bin jest)
I am using node v16.13.2 and ts-jest 27.1.3

jest.mock works for CJS, but not for ESM.
There is a jest.unstable_mockModule and an opened PR to turn it into a stable API, but it's in limbo (Jest author lost motivation).
For more details:
issue jest.mock does not mock an ES module without Babel
PR feat(runtime): add jest.mockModule
Some valuable comments in the issue might help you find a partial solution.
It didn't work for me. I gave up on it and went a different route altogether.

This is a known issue (as mentioned in https://github.com/facebook/jest/issues/10025).
Basically, the issue is that Jest can't hoist the mock before all other imports as it does using babel (https://github.com/facebook/jest/issues/10025#issuecomment-920401080). Using a top-level await along with jest.unstable_mockModule worked for me.
Just to note the example using jest.unstable_mockModule:
import { jest } from "#jest/globals";
const mockFn = jest.fn();
jest.unstable_mockModule("./helpers", () => ({
fn: mockFn
}));
const { fn } = await import("./helpers"); // Needs to be after the mock is declared.

Related

Nestjs Jest Unit Test: Basic test failure - Jest configuration

I have a simple Jest test for my Nest JS project.
The Jest looks like:
import { Test, TestingModule } from '#nestjs/testing';
import { IbmVpcController } from './ibm.vpc.controller';
import { IbmVpcServiceMock } from './ibm.vpc.service.mock';
import { ModuleMocker, MockFunctionMetadata } from 'jest-mock';
import { MOCKED_VPC } from '../../repository/ibm/mock.vpc.data';
const moduleMocker = new ModuleMocker(global);
describe('IbmVpcController', () => {
let controller: IbmVpcController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [IbmVpcController],
providers: [IbmVpcServiceMock]
})
.useMocker((token) => {
if (token === IbmVpcServiceMock) {
return {
list: jest.fn().mockResolvedValue(MOCKED_VPC.VPCs),
get: jest.fn().mockResolvedValue(MOCKED_VPC.VPCs[0]),
create: jest.fn().mockResolvedValue(MOCKED_VPC.VPCs[0]),
update: jest.fn().mockResolvedValue(MOCKED_VPC.VPCs[0]),
};
}
if (typeof token === 'function') {
const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata<any, any>;
const Mock = moduleMocker.generateFromMetadata(mockMetadata);
return new Mock();
}
})
.compile();
controller = module.get<IbmVpcController>(IbmVpcController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
My jest.config.js looks like:
module.exports = {
verbose: true,
preset: "ts-jest",
testEnvironment: "node",
roots: ["./src"],
transform: { "\\.ts$": ["ts-jest"] },
testRegex: "(/__test__/.*|(\\.|/)(spec))\\.ts?$",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
transformIgnorePatterns: [
'<rootDir>/node_modules/',
],
globals: {
"ts-jest": {
tsconfig: {
// allow js in typescript
allowJs: true,
},
},
},
};
However it is failing with the following error:
FAIL apps/protocols/src/ibm/vpc/ibm.vpc.controller.spec.ts
● Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
C:\Users\pradipm\clients\CloudManager\cm_6\occm\client-infra\nest-services\node_modules\axios\index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import axios from './lib/axios.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at Runtime.createScriptFromCode (../../node_modules/jest-runtime/build/index.js:1350:14)
at Object.<anonymous> (../../node_modules/retry-axios/src/index.ts:124:1)
Now able to get it what I am missing in my typescript Nest's Jest configuration.
Basically I tried out some more options also:
I tried out specifying the transformIgnorePatterns as only '/node_modules/'.
Tried out excluding the lodash-es', 'axios'
Tried out transformIgnorePattens as '/lib/' (where axois is there)
Added allowJs: true in the tsconfig.app.json compileOptions.
Any help to get trough my first basic test would be helpful.
With axios version 1.1.2 there's a bug with jest. You can resolve it by adding moduleNameMapper: { '^axios$': require.resovle('axios') } to your jest configuration

Migrating cypress test from old version to latest version throws error

I am trying to migrate my test from Cypress 8.7.0 version to Cypress 10.10.0 version. Installed the latest version and did the below settings, but getting below error.
Using below versions:
Cypress 10.10.0,
"#badeball/cypress-cucumber-preprocessor": "^11.4.0",
node v18.4.0,
#bahmutov/cypress-esbuild-preprocessor": "^2.1.5"
Expected to find a global registry (this usually means you are trying to define steps or hooks in support/e2e.js, which is not supported) (this might be a bug, please report at https://github.com/badeball/cypress-cucumber-preprocessor)
Because this error occurred during a before each hook we are skipping all of the remaining tests.
I have added the error handling in e2e.js file and support/index.js file but still could not resolve this issue. I have .env file which has the environment variable in my root location. Could someone please advise on this issue ?
//Detail error log:
Because this error occurred during a `before each` hook we are skipping all of the remaining tests.
at fail (tests?p=tests/cypress/e2e/login/loginBase.feature:964:15)
at assert (tests?p=tests/cypress/e2e/login/loginBase.feature:971:9)
at assertAndReturn (tests?p=tests/cypress/e2e/login/loginBase.feature:975:9)
at getRegistry (tests?
Cypress version : v10.10.0
//tests/cypress/e2e/login/login.feature
#regression
#login
Feature: Login to base url
Scenario: Login to base url
Given I go to base url
//step defintion:
tests/cypress/stepDefinitions/login.cy.js
import { Given, When, Then, Before, After, And } from "#badeball/cypress-cucumber-preprocessor";
When('I go to base url', () => {
cy.visit(Cypress.config().baseUrl);
})
// tests/cypress/support/index.js file
// Import commands.js using ES2015 syntax:
import './commands'
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
});
//tests/cypress/support/e2e.js
// Import commands.js using ES2015 syntax:
import './commands'
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
})
//.cypress-cucumber-preprocessorrc.json // add this file in project root location
{
"stepDefinitions": [
"[filepath].{js,ts}",
"tests/cypress/stepDefinitions/**/*.{js,ts}"
]
}
// cypress.config.js
const { defineConfig } = require('cypress')
const createBundler = require("#bahmutov/cypress-esbuild-preprocessor");
const addCucumberPreprocessorPlugin = require("#badeball/cypress-cucumber-preprocessor")
const createEsbuildPlugin = require("#badeball/cypress-cucumber-preprocessor/esbuild").createEsbuildPlugin;
const dotenvPlugin = require('cypress-dotenv');
async function setupNodeEvents(on, config) {
await addCucumberPreprocessorPlugin.addCucumberPreprocessorPlugin(on, config);
on(
"file:preprocessor",
createBundler({
plugins: [createEsbuildPlugin(config)],
})
);
//webpack config goes here if required
config = dotenvPlugin(config)
return config;
}
module.exports = defineConfig({
e2e: {
baseUrl: 'https://bookmain.co',
apiUrl: 'https://bookmain.co/api/books/',
specPattern: "tests/cypress/e2e/**/*.feature",
supportFile: false,
setupNodeEvents
},
component: {
devServer: {
framework: "next",
bundler: "webpack",
},
},
});
// package.json
"cypress-cucumber-preprocessor": {
"nonGlobalStepDefinitions": true,
"stepDefinitions": "tests/cypress/stepDefinitions/**/*.{js,ts}",
"cucumberJson": {
"generate": true,
"outputFolder": "tests/cypress/cucumber-json",
"filePrefix": "",
"fileSuffix": ".cucumber"
}
},
In cypress.config.js add the following:
const {dotenvPlugin} = require('cypress-dotenv');
module.exports = (on, config) => {
config = dotenvPlugin(config)
return config
}
This will resolve the issue.

Using crypto.randomInt() in React

I am trying to use crypto.randomInt() Nodejs API in a project bootstrapped with create-react-app that uses Webpack 5. I have tried multiple options like this so far:
// webpack.config.js
module.exports = {
resolve: {
fallback: { crypto: require.resolve('crypto-browserify') },
}
};
And then, npm i crypto-browserify as recommended by the error message.
// Returns Uncaught TypeError: randomInt is not a function
const { randomInt } = import('crypto');
console.log(randomInt(4));
From some threads, I tried:
// package.json
...
"browser": {
"crypto": false
}
...
Still, no luck. I tried import {randomInt} from 'crypto-browserify'. This didn't work either.
What should I do to make this work?

Nest.js Testing Error: Using the "extends Logger" instruction is not allowed in Nest v8. Please, use "extends ConsoleLogger" instead

Here's the problem I have:
I am using my custom Logger in Nest.js:
export class ReportLogger extends ConsoleLogger {
verbose(message: string) {
console.log('【Verbose】Reporting', message);
super.verbose.apply(this, arguments);
}
log(message: string) {
console.log('【Log】Reporting', message);
super.log.apply(this, arguments);
}
}
And the log.interceptor.ts file:
export class LogInterceptor implements NestInterceptor {
constructor(private reportLogger: ReportLogger) {
this.reportLogger.setContext('LogInterceptor');
}
intercept(context: ExecutionContext, next: CallHandler) {
const http = context.switchToHttp();
const request = http.getRequest();
const now = Date.now();
return next
.handle()
.pipe(
tap(() =>
this.reportLogger.log(
`${request.method} ${request.url} ${Date.now() - now}ms`,
),
),
);
}
}
And here's the main.ts file:
async function bootstrap() {
const reportLogger = new ReportLogger();
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
cors: {
origin: ['http://localhost', 'http://localhost:3000'],
credentials: true,
},
bufferLogs: true,
logger: reportLogger,
});
app.useGlobalInterceptors(
new LogInterceptor(reportLogger),
);
setupSwagger(app);
await app.listen(4200);
}
When I run npm run start:dev to run the Nest App on dev, everything works fine. But when I run npm run test:e2e or npm run test on testing, it shows this error:
Using the "extends Logger" instruction is not allowed in Nest v8. Please, use "extends ConsoleLogger" instead.
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [AppModule],
> 12 | }).compile();
| ^
13 |
14 | app = moduleFixture.createNestApplication();
15 | await app.init();
I read the Nest.js doc again, and found the Logging breaking change in the docs. But the question is I have already made my ReportLogger extends ConsoleLogger, why this error shows again? And why it only shows in testing?
I have faced the same problem after upgrading NestJS to version 8.
Later on, I found that package #nestjs/testing had previous version installed and was not upgraded to the latest version. The reason is, previous version of NestJS testing module is using the old Logger.
In order to fix this issue, you just need to upgrade the NestJS testing module.
Run this command for Latest version:
npm i #nestjs/testing#latest
OR specific version
npm i #nestjs/testing#8.0.6 // <--- Change the NestJS version here
After this, just build and run test cases again.
External Links:
NestJS Testing NPM
even with "#nestjs/testing": "^8.0.7" this issue still occurs
class Logger implements LoggerService { ... }
await Test.createTestingModule({
imports: [ApiModule],
})
.setLogger(new Logger())
.compile();
setting logger instance solves that error on my side

How create a module with asynchronous exports in NodeJS with ES6

I want to create a module (install in node_modules) for my projects which call the same API. But when I export my async function I get an error :
index.js Unexpected token (3:21)
You may need an appropriate loader to handle this file type.
Index.js is my module file.
Here's example code:
export default async function({path, method = 'GET', body}, userToken = null, contentType = 'application/json') {
// some code here ...
}
And in my project I import like this :
import invokeApi from 'my_sdk';
It seems that there is an issue with your build configuration on the consumer code side. You must enable async/await when consuming async APIs as it requires regeneration runtime (or install Node 7.6 or later as it brings native async/await support).
If you use this code on the web you might probably need to import 'babel-polyfill' in the entry point file (probably main.js, app.js or index.js) and set up Webpack to use Babel to transpile your code.
Works like a charm using babel-node with following "presets": ["es2015", "stage-0"]:
asyncModule.js
const sleep = async duration =>
new Promise(resolve => setTimeout(resolve, duration))
export default async function({path, method = 'GET', body}, userToken = null, contentType = 'application/json') {
await sleep(500)
return "Hello :)"
}
index.js
import asyncExample from './asyncModule'
const main = async () => {
const result = await asyncExample({path: '/', body: ''});
console.log(result)
main();
npm start
"start": "babel-node --presets es2015 index.js"

Resources