You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:37:09