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

