Nest.js 12 packages are ESM only, and Jest can only load them in its native ESM mode. So a Nest.js 12 test suite on Jest needs some setup before it runs at all.
nestjs-jest-decorators works in that setup, and it also lets you write tests as classes. Suites and tests are plain classes and methods, and test classes are Nest providers, so you use @Inject() instead of app.get(Component).
@TestSuite('Demo Test Suite')
export class DemoTest {
@Inject()
private readonly httpService!: HttpService;
@Test('Get Test')
async testGet(): Promise<void> {
const { data } = await firstValueFrom(this.httpService.get('http://localhost:4343/demo'));
expect(data).toBe('Hello, Test world !');
}
}
@TestSuite is a Jest describe and @Test is a Jest it.
Installation
You need Nest.js 12, Jest 30 and Node.js ^20.19.0, ^22.12.0 or >=24.0.0. To use ts-jest, stay on TypeScript 6.0 or lower.
npm i --save-dev nestjs-jest-decorators jest @jest/globals ts-jest typescript@~6.0.0 @nestjs/testing @types/node
Your tsconfig.json needs these options:
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"types": ["node"],
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Turn on Jest's ESM mode
Add jest.config.mjs:
export default {
testEnvironment: 'node',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1' },
transform: { '^.+\\.ts$': ['ts-jest', { useESM: true }] },
testMatch: ['<rootDir>/test/test.entrypoint.ts'],
verbose: true,
};
Then add the test script to your package.json:
{
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
}
}
ESM mode is still experimental in Jest, so it needs the --experimental-vm-modules flag, and Node.js prints an ExperimentalWarning when it runs.
Boot the app and declare the tests
Create test/test.entrypoint.ts:
import { afterAll, beforeAll } from '@jest/globals';
import { HttpModule } from '@nestjs/axios';
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { declareTests, JestTestModule } from 'nestjs-jest-decorators';
import { DemoTest } from './tests/demo.test.js';
import { YourAppModule } from './your-app/your-app.module.js';
const tests = [DemoTest];
let app: INestApplication;
beforeAll(async () => {
const testingModule = await Test.createTestingModule({
imports: [
YourAppModule, // The app to test
JestTestModule.registerTests(tests, [HttpModule]), // Your tests, and the modules they need
],
}).compile();
app = testingModule.createNestApplication();
await app.listen(4343);
});
afterAll(async () => {
await app.close();
});
declareTests(tests, () => app);
Jest collects tests synchronously, before any beforeAll runs, and it has no way to delay that. That's why declareTests receives a function: tests are declared right away, and the app is only asked for each instance when a test runs.
Share tests with inheritance
Tests declared in a base class run in each subclass decorated with @TestSuite, and a subclass can override a test method:
abstract class CrudTest {
@Test('Lists items')
async list(): Promise<void> {
// ...
}
}
@TestSuite('Users')
export class UsersTest extends CrudTest {}
@TestSuite('Orders')
export class OrdersTest extends CrudTest {}
Run code around every test
Create a class with @TestEventsSuite() and add it to your list of tests. Its hooks run around every test of every suite:
import { TestEvent, TestEventsEnum, TestEventsSuite } from 'nestjs-jest-decorators';
@TestEventsSuite()
export class Events {
@TestEvent(TestEventsEnum.BEFORE_EACH)
async beforeEach(): Promise<void> {
// Runs before each test
}
@TestEvent(TestEventsEnum.AFTER_EACH)
async afterEach(): Promise<void> {
// Runs after each test
}
}
Run a single suite or test
Pick one with an environment variable. Names must match exactly, and Jest fails if no test matches, so a typo won't look like a successful run:
SUITETORUN="Demo Test Suite" npm test
TESTTORUN="Get Test" npm test
Try it
The library is MIT licensed. You can find it on npm and GitHub, where issues and suggestions are welcome.
Top comments (0)