Nx: migrate Jest to Vitest for NestJS
Guan Xin Wang
Technical Solution Engineer | Software Engineer | Software Developer | Data Engineer
Overview
Migrating from Jest to Vitest in a NestJS project can significantly improve testing efficiency and performance. This article will guide you through the migration process, focusing on projects using the Nx monorepo structure.
NestJS projects typically use Jest as the default testing framework. However, Vitest has emerged as a mature alternative that offers several advantages:
We'll explore each concept step-by-step, providing practical examples and best practices along the way. By the end of this article, you'll understand how to:
Monorepo
A monorepo is a version control strategy where multiple projects are stored in a single repository. This approach offers several benefits:
For example, a monorepo could contain both a frontend React application and a backend NestJS API, allowing developers to manage the entire stack in one place. This structure facilitates easier updates and ensures consistency across the entire application ecosystem.
Nx
Nx is a powerful monorepo management tool that enhances development workflows in large-scale projects. Key features of Nx include:
Nx's popularity stems from its ability to provide a unified interface for interacting with different frameworks, making it easier to manage complex, multi-project repositories. The version used in this article (18+) offers improved performance and additional features compared to earlier versions.
Vitest
Vitest is a next-generation testing framework designed to work seamlessly with Vite. Its key advantages include:
Vitest leverages Vite's HMR capabilities to provide near-instantaneous feedback during test runs. This speed improvement, combined with its compatibility with Jest, makes it an attractive option for projects looking to enhance their testing workflow without a complete overhaul of existing test suites.
领英推荐
@nx/vite
Workspace
Run nx add @nx/vite to install vite and run nx g vitest --project <projectName> to generate relevant files. With Nx, there could be more libraries to be tested and we can state it in the vitest.workspace.ts
export default ['**/*/vite.config.ts', '**/*/vitest.config.ts']; // by default
// import { defineWorkspace } from 'vitest/config';
// export default defineWorkspace([
// 'apps/*',
// 'libs/*',
// // Add more patterns as needed
// ]);
Vite config or Vitest config
The default setup is:
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/nest-backend',
plugins: [nxViteTsPaths()],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
globals: true,
cache: {
dir: '../node_modules/.vitest/nest-backend',
},
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/nest-backend',
provider: 'v8',
},
},
});
Here’s another project using vitest.config.ts. I use extra swc to further improve the performance.
import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: [
'**/*.{test,spec,e2e-spec}.?(c|m)[jt]s?(x)',
'./test/**/*.e2e-spec.ts',
],
reporters: ['verbose'],
testTimeout: 120000,
},
plugins: [
nxViteTsPaths(),
swc.vite({
module: { type: 'es6' },
}),
],
});
NestJS Migration
Originally, the Jest implementations:
import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/sequelize';
import { TaskService } from './task.service';
import { Task } from './entities/task.entity';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';
import { ModuleMocker, MockFunctionMetadata } from 'jest-mock';
const moduleMocker = new ModuleMocker(global);
describe('TaskService', () => {
let service: TaskService;
let mockTaskRepository: any;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [TaskService],
})
.useMocker((token) => {
if (token === getModelToken(Task)) {
return {
create: jest.fn(),
findAll: jest.fn(),
findOne: jest.fn(),
update: jest.fn(),
destroy: jest.fn(),
};
}
if (typeof token === 'function') {
const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata<any, any>;
const Mock = moduleMocker.generateFromMetadata(mockMetadata);
return new Mock();
}
})
.compile();
service = module.get<TaskService>(TaskService);
mockTaskRepository = module.get(getModelToken(Task));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
It is easy as that:
import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/sequelize';
import { TaskService } from './task.service';
import { Task } from './entities/task.entity';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';
import { describe, expect, it, vi } from 'vitest'; // import it explictily
describe('TaskService', () => {
let service: TaskService;
let mockTaskRepository: any;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [TaskService],
})
.useMocker((token) => {
if (token === getModelToken(Task)) {
return {
create: vi.fn(), // replace jest to vi
findAll: vi.fn(),
findOne: vi.fn(),
update: vi.fn(),
destroy: vi.fn(),
};
}
if (typeof token === 'function') {
return vi.fn();
}
})
.compile();
service = module.get<TaskService>(TaskService);
mockTaskRepository = module.get(getModelToken(Task));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
The main differences are replacing the moduleMocker and when the token equals function.
Conclusion
Migrating from Jest to Vitest is straightforward. The main differences involve replacing Jest-specific functions like moduleMocker with their Vitest counterparts, such as using vi.fn() instead of jest.fn(). The process is relatively simple and improves performance due to Vitest's efficiency, making it a worthwhile migration.