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

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?

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

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).

How to fail the test if an RxJS subscription in the call chain throws an error?

Using an automated test should allow to find programming errors inside the called code. One programming error is to have an unhandled exception in an RxJS subscribe callback. However, in the following example, jest will simply ignore the error and pass the test.
import { of } from "rxjs";
test("Observable subscription should fail", async () => {
const testObservable$ = of(0, 1, 2, 3);
testObservable$.subscribe((_) => {
throw new Error("Something went wrong");
});
});
The test uses the following minimal jest.config.js:
module.exports = {
preset: "ts-jest/presets/js-with-ts",
transform: { "^.+\\.ts?$": "ts-jest" },
testEnvironment: "node",
testRegex: ".*\\.spec?\\.ts$",
silent: false,
verbose: true,
};
How can I adjust either the test, or the Jest configuration, to make the test fail on an error within the subscription?

Jasmine ignoring typescript test files?

This is my first time making a project with Jasmine, and I'm following a tutorial but right off the bat having issues.
I've installed jasmine-node, typings, and typescript. I also ran:
typings install dt~jasmine --save-dev --global
For Jasmine typescript.
Now I have a test file in my ./spec folder that looks like this:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { DatePickerComponent } from '../src/components/via-datepicker.component';
import * as moment from 'moment';
const Moment: any = (<any>moment).default || moment;
describe('DatePickerComponent', () => {
let component: DatePickerComponent;
let fixture: ComponentFixture<DatePickerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DatePickerComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DatePickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should open when clicked', () => {
fixture.debugElement.nativeElement.querySelector('body').click();
fixture.whenStable().then(() => {
expect(component.opened);
});
component.close();
});
describe('While open', () => {
beforeEach(() => {
component.open();
});
describe('Pressing the "Today\'s date" button', () => {
it('should set the value of the picker to the current date and close it', () => {
fixture.debugElement.nativeElement.querySelector('.datepicker-buttons button').click();
expect(Moment().isSame(component.value, 'day') && Moment().isSame(component.value, 'month'));
expect(!component.opened);
});
});
describe('Clicking on a date', () => {
it('should change the value of the picker and close it', () => {
let oldValue: any = component.value;
fixture.debugElement.nativeElement.querySelectorAll('.day')[10].click();
expect(!component.opened);
expect(!component.value.isSame(oldValue));
});
});
});
});
But when I run this command:
node_modules/jasmine-node/bin/jasmine-node spec
I get this result:
Finished in 0 seconds
0 tests, 0 assertions, 0 failures, 0 skipped
So clearly my test file is being ignored. Or maybe I'm missing some library? Would I receive an error message if this were the case? The main issue here is that I'm not being given much direction as to what the issue is, other than Jasmine doesn't seem to "see" the test file for some reason.
Just trying to move forward with my project. Any advice would be greatly appreciated.
It appears as if your test runner doesn't know that you're trying to run typescript tests. Are you using Karma as your test runner? If so, you need to add your Typescript files to your karma.config file and install karma-typescript and configure your karma.config file similar to what is shown below. Pay close attention to the addition to the frameworks, files, and preprocessors sections.
karma.config
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine', 'karma-typescript'],
// list of files / patterns to load in the browser
files: [
{ pattern: "app/tests/**/*.spec.js"}
],
// list of files to exclude
exclude: [
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
"app/tests/**/*.spec.ts": ["karma-typescript"]
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: [],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true
})
};

Resources