Nestjs Jest Unit Test: Basic test failure - Jest configuration - jestjs

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

Related

TypeError: Cannot read properties of undefined (reading 'listen') when testing with Jest, Supertest, Express, Typescript

Problem:
when running jest and supertest, i get an error before it reaches the actual tests i've described. Server runs fine using the start script, app is defined. But when running test script, app is undefined.
Background:
I'm fairly new to typescript and this is the first time I'm using any kind of testing.
I want to seperate the server instance, as seen on several blog posts and tutorials, as i plan on running multiple tests in different files.
If you have any suggestions even if they are something I already tried, ill try again and let you know. I am at my wits end so any help is much apprecitated. Thank you.
Error:
FAIL src/components/users/user.test.ts
● Test suite failed to run
TypeError: Cannot read properties of undefined (reading 'listen')
6 |
7 | dbConnection();
> 8 | export const server = app.listen(config.server.port, () => {
| ^
9 | logger.info(`Server is running on port: ${config.server.port}`);
10 | });
11 |
at Object.<anonymous> (src/index.ts:8:27)
at Object.<anonymous> (src/library/exitHandler/exitHandler.ts:2:1)
at Object.<anonymous> (src/library/errorHandler/errorHandler.ts:2:1)
at Object.<anonymous> (src/middleware/validateSchema.ts:3:1)
at Object.<anonymous> (src/components/users/routes.ts:2:1)
at Object.<anonymous> (src/server.ts:2:1)
at Object.<anonymous> (src/components/users/user.test.ts:2:1)
user.test.ts
import request from 'supertest';
import app from '../../server';
describe('User registration', () => {
it('POST /register --> return new user instance', async () => {
await request(app) // error occurs when reaching this point
.post('/user/register')
.send({
firstName: 'Thomas',
lastName: 'Haek',
email: 'thomashaek#gmail.com',
password: '12345678aA',
confirmPassword: '12345678aA'
})
.expect(201)
.then((response) => {
expect(response.body).toEqual(
expect.objectContaining({
_id: expect.any(String),
firstName: expect.any(String),
lastName: expect.any(String),
email: expect.any(String),
token: expect.any(String)
})
);
});
});
});
server.ts
import express, { Application } from 'express';
import userRouter from './components/users/routes';
import { routeErrorHandler } from './middleware/errorHandler';
import httpLogger from './middleware/httpLogger';
import './process';
const app: Application = express();
app.use(httpLogger);
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use('/user', userRouter);
app.use(routeErrorHandler);
export default app
index.ts
import { createHttpTerminator } from 'http-terminator';
import config from './config/config';
import dbConnection from './config/dbConnection';
import logger from './library/logger';
import app from './server'
dbConnection();
export const server = app.listen(config.server.port, () => {
logger.info(`Server is running on port: ${config.server.port}`);
});
export const httpTerminator = createHttpTerminator({ server });
package.json scripts
"scripts": {
"test": "env-cmd -f ./src/config/test.env jest --watchAll",
"start": "env-cmd -f ./src/config/dev.env node build/index.js",
},
tsconfig.json
{
"compilerOptions": {
"outDir": "./build",
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"module": "commonjs",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
},
"include": ["src/**/*"]
}
jest.config.ts
import { Config } from 'jest';
/** #type {import('ts-jest').JestConfigWithTsJest} */
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['./src'],
moduleFileExtensions: ['js', 'ts'],
clearMocks: true,
collectCoverage: true,
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: ['/node_modules/', '/src/config/'],
coverageProvider: 'v8',
coverageReporters: ['json', 'text', 'lcov', 'clover'],
verbose: true
};
export default config;
exitHandler.ts
import mongoose from 'mongoose';
import { httpTerminator, server } from '../..';
import logger from '../logger';
class ExitHandler {
public async handleExit(code: number, timeout = 5000): Promise<void> {
try {
logger.info(`Attempting graceful shutdown with code: ${code}`);
setTimeout(() => {
logger.info(`Forcing a shutdown with code: ${code}`);
process.exit(code);
}, timeout).unref();
if (server.listening) {
logger.info('Terminating HTTP connections');
await httpTerminator.terminate();
await mongoose.connection.close();
}
logger.info(`Exiting gracefully with code ${code}`);
process.exit(code);
} catch (error) {
logger.error(error);
logger.error(
`Unable to shutdown gracefully... Forcing exit with code: ${code}`
);
process.exit(code);
}
}
}
export const exitHandler = new ExitHandler();
Things I've tried:
using the same env files for both test and server script (same error)
messing around with the tsconfig and jest config files (same error)
using module.exports = app instead of export default app or export const server = app (same error)
commenting out all the middleware and routes and just exporting the app (same error)
I believe this is caused by circular dependency. From the error stack
at Object.<anonymous> (src/index.ts:8:27)
at Object.<anonymous> (src/library/exitHandler/exitHandler.ts:2:1
…
at Object.<anonymous> (src/server.ts:2:1)
at Object.<anonymous> (src/components/users/user.test.ts:2:1)
I see that server.ts deps on exitHandler.ts which in turn deps on index.ts. But in index.ts you import app from './server' forming a circle.
More specifically, in order for app from server.ts to be created, it needs exitHandler, but that thing needs index, and index needs server. It’s like recursion without a base case return. Unlike indefinite function recursion, dependency resolution will just give you app as undefined.
Thus you need to break the circle. Use some dependency injection trick to break the tie between exitHandler and index will do.
If you don’t know how to do that, post the exitHandler.ts code and I’ll follow up.
Instead of import { httpTerminator, server } from '../..'; try this:
let server, httpTerminator;
export function injectDependency(s, h) {
server = s;
httpTerminator = h;
}
Now in index.ts
import { injectDependency } from "…/exitHandler"
injectDependency(server, httpTerminator);

How do I serve static files in NestJS using fastify?

How does one serve static files in NestJS using fastify? I can't seem to find any recent examples of setting this up properly. I have my main.ts set up like this:
main.ts
// This must be the first thing imported in the app
import 'src/tracing';
import * as winston from 'winston';
import fastifyStatic, { FastifyStaticOptions } from '#fastify/static';
import { NestFactory } from '#nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '#nestjs/platform-fastify';
import { path } from 'app-root-path';
import { WinstonModule } from 'nest-winston';
import { doc } from 'prettier';
import { AppModule } from 'src/app.module';
import join = doc.builders.join;
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
{
logger: WinstonModule.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
transports: [new winston.transports.Console()],
}),
rawBody: true,
},
);
await app.register(require('#fastify/static'), {
root: require('app-root-path').resolve('/client'),
prefix: '/client/', // optional: default '/'
});
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore
app.get('/another/path', function (req, reply) {
reply.sendFile('index.html');
});
app.enableShutdownHooks(); // terminus needs this to listen for SIGTERM/SIGKILL
await app.listen(3002, '0.0.0.0');
console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();
The static file I'm attempting to serve is client/index.html.
However, when I run my app I get the following error: Nest could not find /another/path element (this provider does not exist in the current context).
I've also tried setting up my app.module.ts Modules like this:
app.module.ts
#Module({
imports: [
...configModules,
...domainModules,
...libraryModules,
ServeStaticModule.forRoot({
rootPath: require('app-root-path').resolve('/client'),
renderPath: '/client/*',
}),
],
controllers: [AppController],
providers: [AppService],
})
This leads to the following error:
/Users/ewu/Desktop/Projects/janus/node_modules/#nestjs/platform-fastify/node_modules/fastify/lib/route.js:286
throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url)
^
FastifyError: Method 'HEAD' already declared for route '/'
at Object.addNewRoute (/Users/ewu/Desktop/Projects/janus/node_modules/#nestjs/platform-fastify/node_modules/fastify/lib/route.js:286:19)
at Object.route (/Users/ewu/Desktop/Projects/janus/node_modules/#nestjs/platform-fastify/node_modules/fastify/lib/route.js:211:19)
at Object.prepareRoute (/Users/ewu/Desktop/Projects/janus/node_modules/#nestjs/platform-fastify/node_modules/fastify/lib/route.js:144:18)
at Object._head [as head] (/Users/ewu/Desktop/Projects/janus/node_modules/#nestjs/platform-fastify/node_modules/fastify/fastify.js:247:34)
at fastifyStatic (/Users/ewu/Desktop/Projects/janus/node_modules/#fastify/static/index.js:370:17)
Here are the relevant packages and their versions:
"#nestjs/serve-static": "^3.0.0",
"fastify-static": "^4.7.0",
"fastify": "^4.8.1",
"#nestjs/platform-fastify": "^9.1.2",
"#fastify/static": "^6.0.0",
I'm using version 9.0.0 of Nest and v16.15.0 of Node.
You most likely have a #Get() under a #Controller() (most likely your AppController) which is binding the GET / route already. Fastify won't let you bind two handlers to the same route. Because of this, you either need to change the #Get() to have some sort of route associated with it, change the ServeStaticModule to have a different served route, or use a global prefix to modify the rest of the server routes (I believe this leaves the server static module unaffected).

" Preset solid-jest not found." error when using jest

I would like to use jest and solid-jest to test one of my solid-js components. For this I created a configuration so that jest works solid-js. But, whenever I run the npm test command, I get the following error:
● Validation Error:
Preset solid-jest not found.
I searched for solutions on the Internet, but nothing about it. Here is the content of my jest.config.js file :
module.exports = async () => {
return {
verbose: true,
};
};
module.exports = {
preset: 'solid-jest',
testEnvironment: 'node',
transform: {
'^.+\\.jsx?$': 'solid-jest',
},
transformIgnorePatterns: ['./node_modules/'],
};
I don't know what's the problem.

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

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.

Supertest: You are trying to `import` a file after the Jest environment has been torn down

I'm using supertest to test api route in a node/typescript application. The server crashes with this error:
ReferenceError: You are trying to import a file after the Jest environment has been torn down.
Here is the test:
import request from "supertest";
import { server } from "../index";
describe("GET /user/:id", () => {
it("return user information", (done) => {
request(server)
.get("/user/123")
.set("Cookie", ["_id=567;locale=en"])
.expect(200, done);
});
});
The jest config (which has a linter error: 'module' is not defined.)
module.exports = {
roots: ["<rootDir>/src"],
testMatch: [
"**/__tests__/**/*.+(ts|tsx|js)",
"**/?(*.)+(spec|test).+(ts|tsx|js)",
],
transform: {
"^.+\\.(ts|tsx)$": "ts-jest",
},
testEnvironment: "node",
};
How to fix this?

Resources