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

ts-jest全局变量批量运行测试时未设置,但单文件测试时正常的问题求助

ts-jest全局变量批量运行测试时未设置,但单文件测试时正常的问题求助

各位好,我最近在配置 Jest 全局启动/关闭 Express 服务器时遇到了一个诡异的问题:当我单独运行依赖服务器的测试文件(比如 npm test tests/server.test.ts)时,所有测试都能正常通过,服务器也能正确启动;但当我执行npm test批量跑所有测试时,所有依赖这个全局服务器的测试都会直接失败,报错提示global.__SERVER__是undefined。

我已经配置了globalSetup和globalTeardown脚本,也扩展了 Node 的 Global 类型来声明__SERVER__变量,下面是我的相关配置和代码,麻烦大家帮忙排查下哪里出了问题?


相关代码与配置

1. 全局类型声明(tests/types/global.d.ts)

export { };
declare global {
  namespace NodeJS {
    interface Global {
      __SERVER__?: {
        server: import('http').Server;
        port: number;
      };
    }
  }
}

2. 全局服务器启动脚本(tests/setup/serverSetup.ts)

import { getServerInstance } from '../../src/server';

export default async function globalSetup(): Promise<void> {
  try {
    const server = getServerInstance(3000)
    const address = server.address();
    if (!address || typeof address === 'string') {
      throw new Error('Failed to get server address');
    }
    const port = address.port;
    console.log(`Global server started on port ${port}`);
    global.__SERVER__ = { server, port };
  } catch (error) {
    console.error('Global setup failed:', error);
    throw error;
  }
}

3. Jest 核心配置(./jest.config.ts)

import { pathsToModuleNameMapper } from 'ts-jest';

export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  verbose: true,
  moduleNameMapper: pathsToModuleNameMapper({}, { prefix: '<rootDir>/' }),
  moduleDirectories: ['node_modules', 'src'],
  globalSetup: '<rootDir>/tests/setup/serverSetup.ts',
  globalTeardown: '<rootDir>/tests/setup/serverTeardown.ts',
  testTimeout: 30000,
  moduleFileExtensions: ['ts', 'js'],
  transform: {
    '^.+\\.ts$': ['ts-jest', {
      tsconfig: '<rootDir>/tsconfig.json',
    }],
  },
  globals: {
    'ts-jest': {
      tsConfig: './tsconfig.json',
    }
  }
};

4. TypeScript 配置(./tsconfig.json)

{
  "exclude": ["tests", "jest.config.ts"],
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "rootDir": "./src",
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@src/*": ["src/*"]
    },
    "types": ["jest", "node", "./tests/types/global"],
    "resolveJsonModule": true,
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

5. 示例测试文件(tests/server.test.ts)

import supertest from "supertest";

let agent: supertest.SuperAgentTest;

beforeAll(async () => {
  agent = supertest.agent(global.__SERVER__!.server);
  await setupTestDatabase();
}, 10000);

// 测试用例...

我观察到的现象

  • 单文件测试时,控制台会正常打印Global server started on port 3000,说明globalSetup脚本执行正常;
  • 批量运行所有测试时,控制台完全没有这个启动日志,看起来globalSetup要么没执行,要么执行后global变量没有传递到测试进程中。

我尝试的排查方向与解决方案建议

可能原因1:Jest 并行测试导致全局上下文不共享

Jest 默认会并行运行多个测试文件(每个文件对应一个独立进程),而globalSetup是在主进程中执行的,主进程的global变量不会自动同步到子测试进程。单独运行一个文件时,Jest 不会启用并行,所以能直接拿到主进程的global;但批量运行时开启了多进程,每个测试进程的global都是全新的,自然拿不到__SERVER__。

解决方案:用临时文件传递服务器信息
修改serverSetup.ts,把服务器端口写入临时文件,而不是依赖global传递:

import { getServerInstance } from '../../src/server';
import fs from 'fs/promises';
import path from 'path';

export default async function globalSetup(): Promise<void> {
  try {
    const server = getServerInstance(3000)
    const address = server.address();
    if (!address || typeof address === 'string') {
      throw new Error('Failed to get server address');
    }
    const port = address.port;
    console.log(`Global server started on port ${port}`);
    
    // 写入临时文件保存端口
    const tempFile = path.join(__dirname, '../.server-port');
    await fs.writeFile(tempFile, port.toString());
    
    // 服务器实例仅在 teardown 时需要,仍存在主进程 global 中
    global.__SERVER__ = { server, port };
  } catch (error) {
    console.error('Global setup failed:', error);
    throw error;
  }
}

在globalTeardown.ts中清理临时文件:

import fs from 'fs/promises';
import path from 'path';

export default async function globalTeardown(): Promise<void> {
  if (global.__SERVER__) {
    global.__SERVER__.server.close();
    console.log('Global server closed');
  }
  // 删除临时文件
  const tempFile = path.join(__dirname, '../.server-port');
  try {
    await fs.unlink(tempFile);
  } catch (e) {
    // 忽略文件不存在的错误
  }
}

修改测试文件读取临时文件:

import supertest from "supertest";
import fs from 'fs/promises';
import path from 'path';

let agent: supertest.SuperAgentTest;
let serverPort: number;

beforeAll(async () => {
  // 读取临时文件中的端口
  const tempFile = path.join(__dirname, '../.server-port');
  const portStr = await fs.readFile(tempFile, 'utf8');
  serverPort = parseInt(portStr, 10);
  
  agent = supertest.agent(`http://localhost:${serverPort}`);
  await setupTestDatabase();
}, 10000);

可能原因2:Jest 配置路径错误

可以先关闭并行测试验证问题:在jest.config.ts中添加maxWorkers: 1,强制单进程运行所有测试。如果这样能正常通过,就确认是并行导致的问题;如果还是失败,就检查globalSetup的路径是否正确,比如用绝对路径代替<rootDir>变量尝试。

可能原因3:TypeScript 类型加载问题

虽然这不是运行时undefined的直接原因,但可以优化类型配置:在tsconfig.json的compilerOptions中添加"typeRoots": ["./node_modules/@types", "./tests/types"],确保全局类型能被所有测试进程识别。


内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 13:28:01