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

Node.js中Jest单元测试遇环境销毁后导入文件报错,求解决方案

解决Jest测试中"You are trying to import a file after the Jest environment has been torn down"错误

问题场景

在Node项目中使用Jest为PreorderController类编写单元测试时,执行npm run test命令出现以下错误:

ReferenceError: You are trying to `import` a file after the Jest environment has been torn down. From __tests__/calendar/calendarEvents.test.ts.

测试代码(已简化用于调试):

import { PreorderController } from '../../src/classes/libraryofthings/PreorderController';
import { MongoServer } from './db/MongoServer';
import { afterAll, beforeAll, describe, expect, jest, test } from '@jest/globals';

describe('jest testing', () => {
    let mongoServer;
    beforeAll(async () => {
        mongoServer = new MongoServer();
        await mongoServer.start();
    })
    afterAll(async () => {
        await mongoServer.stop();
    })
    test('it saves a new preorder using the preorder controller class', () => {
        const controller = new PreorderController();
    })
})

PreorderController核心代码(包含可疑的sleep定时器逻辑):

import { Web3Controller } from "../web3/Web3Controller";

export class PreorderController {
    private web3Controller: Web3Controller;
    private timeout: number;

    constructor() {
        this.timeout = 1000;
        this.web3Controller = new Web3Controller();
    }

    async updateStatus(id: string, newStatus: string, blockNumber: number, comment?: string, itemWasFine?: boolean): Promise<void> {
        const timestamp = await this.web3Controller.getBlockTimestamp(blockNumber);
        const preorder = await this.getPreorder(id);
        if (!preorder) {
            await sleep(this.timeout);
            return this.updateStatus(id, newStatus, blockNumber, comment, itemWasFine);
        }
        // ...其他业务逻辑
    }
}

const sleep = (ms: number) => {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

怀疑错误与自定义sleep函数中的setTimeout有关,但无法确定具体触发原因。

问题本质

该错误核心是:测试执行完毕、Jest销毁测试环境后,仍有未完成的异步任务(如定时器、未结束的Promise)在运行,这些任务可能尝试访问已被销毁的资源或触发模块导入操作,导致报错。

当前场景下的可能触发点:

  • PreorderController构造函数初始化的Web3Controller包含未完成的异步操作
  • 类中sleep函数的setTimeout即使未被测试代码直接调用,也可能因模块加载、依赖初始化残留异步任务
  • updateStatus方法的递归逻辑可能在测试环境销毁后仍有调用队列

解决方案

1. 确保测试用例处理异步逻辑

即使测试仅做实例化操作,若类的构造函数或依赖包含异步逻辑,需将测试标记为async,确保所有异步任务在测试结束前完成:

test('it saves a new preorder using the preorder controller class', async () => {
    const controller = new PreorderController();
    expect(controller).toBeInstanceOf(PreorderController);
})

2. 清除所有定时器

在测试周期的afterEach或afterAll阶段清除所有定时器,避免残留异步任务:

afterEach(() => {
    jest.clearAllTimers();
})

afterAll(async () => {
    // 给残留异步任务留一点完成时间
    await new Promise(resolve => setTimeout(resolve, 100));
    await mongoServer.stop();
})

3. 模拟外部依赖与异步函数

通过Jest模拟Web3Controller和sleep函数,避免真实的异步操作和定时器触发:

// 模拟Web3Controller,替换真实异步方法
jest.mock('../../src/classes/web3/Web3Controller', () => {
    return {
        Web3Controller: jest.fn().mockImplementation(() => {
            return {
                getBlockTimestamp: jest.fn().mockResolvedValue(Date.now())
            };
        })
    };
});

// 模拟sleep函数,立即resolve,避免真实定时器
jest.mock('../../src/classes/libraryofthings/PreorderController', () => {
    const originalModule = jest.requireActual('../../src/classes/libraryofthings/PreorderController');
    return {
        ...originalModule,
        sleep: jest.fn().mockResolvedValue(undefined)
    };
});

4. 调整Jest配置排查问题

在jest.config.js中添加以下配置,帮助检测未关闭的资源:

module.exports = {
    testEnvironment: 'node',
    detectOpenHandles: true, // 检测未关闭的句柄(如数据库连接、定时器)
    // forceExit: true, // 仅临时使用,强制退出Jest(不推荐长期使用)
}

5. 检查数据库连接完整性

确保MongoServer的start和stop方法真正完成数据库的连接与断开:

  • start方法需等待数据库连接成功后再resolve
  • stop方法需等待数据库连接完全关闭后再resolve

内容的提问来源于stack exchange,提问作者Allennick

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 13:11:07