Jest E2E测试复用NestJS单例时依赖找不到问题排查
解决NestJS Jest E2E测试中复用应用实例时依赖找不到的问题
问题根源
Jest的globalSetup运行在独立于测试进程的上下文中,即使设置了maxWorkers:1,其创建的全局变量也无法被测试代码正确共享。你在globalSetup中初始化的Nest应用,其模块元数据与测试进程中加载的模块上下文不匹配,导致app.get()无法定位到依赖。
正确复用NestJS应用实例的方案
1. 调整Jest配置,迁移应用初始化逻辑
将应用初始化从globalSetup移至setupFilesAfterEnv,后者运行在测试进程上下文中,能与测试代码共享全局变量,适合创建需要复用的单例应用。
修改jest.config.json(保留MySQL容器的全局启停逻辑):
{ // 其他配置... "setupFilesAfterEnv": ["./test/jest.setup.ts"], "globalSetup": "./test/mysql-global-setup.ts", "globalTeardown": "./test/mysql-global-teardown.ts", "maxWorkers": 1 }
2. 重构单例应用初始化模块
修改test/test-nest-application.ts,确保应用在测试进程上下文内创建单例:
import { Test } from '@nestjs/testing'; import { AppModule } from '../src/app.module'; import { WinstonLoggerService } from '../src/common/logger/logger.service'; import * as cookieParser from 'cookie-parser'; import { InternalExceptionsFilter } from '../src/common/filters/internal-exceptions.filter'; import { HttpExceptionsFilter } from '../src/common/filters/http-exception.filter'; import { INestApplication, ValidationPipe } from '@nestjs/common'; // 声明全局变量类型,避免类型报错 declare global { namespace NodeJS { interface Global { testApp: INestApplication; } } } // 获取单例测试应用 export const getTestApp = async (): Promise<INestApplication> => { if (!global.testApp) { const moduleFixture = await Test.createTestingModule({ imports: [AppModule], }).compile(); const app = moduleFixture.createNestApplication(); // 保留你的应用配置(过滤器、管道、中间件等) app.use(cookieParser()); app.useGlobalPipes(new ValidationPipe()); app.useGlobalFilters(new InternalExceptionsFilter(), new HttpExceptionsFilter()); // 替换为你原本的应用设置代码 await app.init(); global.testApp = app; } return global.testApp; }; // 全局销毁应用,用于测试结束后清理资源 export const destroyTestApp = async (): Promise<void> => { if (global.testApp) { await global.testApp.close(); delete global.testApp; } };
3. (可选)在全局前置钩子中预先初始化应用
如果希望所有测试套件启动前就完成应用初始化,可在jest.setup.ts中添加:
import { getTestApp } from './test-nest-application'; beforeAll(async () => { await getTestApp(); });
4. 修改测试套件代码
测试套件中直接获取单例应用,无需重复初始化:
// test/rss/rss-e2e-spec.ts import { getTestApp } from '../test-nest-application'; import { INestApplication } from '@nestjs/common'; import { RssRepository } from '../../src/modules/rss/repositories/rss.repository'; import { RssAcceptRepository } from '../../src/modules/rss/repositories/rss-accept.repository'; import { RedisService } from '../../src/common/redis/redis.service'; describe('Rss Accept E2E Test', () => { let app: INestApplication; let rssRepository: RssRepository; let rssAcceptRepository: RssAcceptRepository; let redisService: RedisService; beforeAll(async () => { app = await getTestApp(); // 现在可正确获取依赖,应用与测试上下文一致 rssRepository = app.get(RssRepository); rssAcceptRepository = app.get(RssAcceptRepository); redisService = app.get(RedisService); }); describe('POST /api/rss/accept/{rssId}', () => { // 测试用例代码... }); });
5. 全局销毁应用,清理资源
在mysql-global-teardown.ts中添加应用销毁逻辑,确保测试结束后释放资源:
import { destroyTestApp } from './test-nest-application'; import { MySqlContainer } from '@testcontainers/mysql'; export default async () => { // 销毁MySQL容器的原有代码... await destroyTestApp(); };
关键注意事项
- 保持
maxWorkers:1,多进程模式下每个进程会创建独立的应用实例,单例逻辑失效。 - 若测试需要隔离状态,可在
afterEach或afterAll中清理数据库、缓存等数据,无需重启应用。 - 禁止在
globalSetup中初始化Nest应用,其上下文与测试进程不共享,会导致依赖元数据不匹配。
内容的提问来源于stack exchange,提问作者Dino_coder
相关产品推荐
相关产品推荐

