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

Vitest动态导入异常:首次测试路由返回404后续正常

问题:Vitest首次测试Express路由返回404,后续测试正常

在Node.js + Express API中使用Vitest执行测试时遇到异常:首次运行测试时接口返回404错误,但后续所有测试运行均返回200。调试发现首次运行时动态导入的路由代码未执行,后续运行正常,怀疑与Vitest缓存机制有关。

路由加载代码

import { Express, Router } from 'express';
import fastGlob from 'fast-glob';

export default (app: Express): void => {
  const router = Router();
  app.use('/api', router);

  const files = fastGlob.sync('**/src/main/router/routes/**.routes.ts');

  files.map(async (file: string) => {
    const route = (await import(`../../../${file}`)).default;

    route(router);
  });
};

测试文件代码

import request from 'supertest';
import { describe, beforeAll, afterAll, it, expect } from 'vitest';

import app from '../../../src/main/config/app';
import datasource from '../../../src/infra/db/postgres/helpers/data-source.helper';
import { UserOrmModel } from '../../../src/infra/db/postgres/models/user.model';

describe('Account Routes', () => {
  beforeAll(async () => {
    await datasource.initialize();
  });

  afterAll(async () => {
    await datasource.getRepository(UserOrmModel).clear();
    await datasource.destroy();
  });

  const makeFakeUserData = () => ({
    name: 'John Doe',
    email: 'john_doe_int@mail.com',
    password: '123456',
    passwordConfirmation: '123456',
  });

  describe('POST /signup', async () => {
    it('should return 200 if valid data is passed', async () => {
      const httpResponse = await request(app)
        .post('/api/signup')
        .send(makeFakeUserData());

      expect(httpResponse.statusCode).toBe(200);
    });
});

根因分析

  1. 异步路由加载未等待完成:路由加载函数中使用files.map(async (file) => { ... }),但map不会等待异步函数执行完成,直接返回Promise数组,而整个路由加载函数是同步的,没有等待所有Promise resolve。导致测试启动时路由还未挂载到Express实例,首次请求触发404。
  2. Vitest模块缓存:后续测试运行时,Vitest会缓存已加载的模块(包括app实例),此时首次测试时的异步路由挂载流程已完成,所以后续请求能正常命中路由返回200。

解决方案

1. 修改路由加载函数为异步,确保挂载完成

将路由加载改为异步函数,用Promise.all等待所有路由文件导入并挂载:

import { Express, Router } from 'express';
import fastGlob from 'fast-glob';

export default async (app: Express): Promise<void> => {
  const router = Router();
  app.use('/api', router);

  const files = fastGlob.sync('**/src/main/router/routes/**.routes.ts');

  // 等待所有异步导入与路由挂载完成
  await Promise.all(files.map(async (file: string) => {
    const route = (await import(`../../../${file}`)).default;
    route(router);
  }));
};

2. 在测试中等待App初始化完成

修改app的导出逻辑,确保路由加载完成后再返回实例:

调整app配置文件(src/main/config/app.ts)

import express from 'express';
import loadRoutes from './load-routes'; // 你的路由加载函数

const app = express();
app.use(express.json()); // 其他中间件配置

// 导出初始化函数,而非直接导出app
export const initApp = async () => {
  await loadRoutes(app);
  return app;
};

export default initApp;

调整测试文件

import request from 'supertest';
import { describe, beforeAll, afterAll, it, expect } from 'vitest';

import initApp from '../../../src/main/config/app';
import datasource from '../../../src/infra/db/postgres/helpers/data-source.helper';
import { UserOrmModel } from '../../../src/infra/db/postgres/models/user.model';

describe('Account Routes', () => {
  let app;

  beforeAll(async () => {
    await datasource.initialize();
    // 等待app初始化与路由挂载完成
    app = await initApp();
  });

  afterAll(async () => {
    await datasource.getRepository(UserOrmModel).clear();
    await datasource.destroy();
  });

  const makeFakeUserData = () => ({
    name: 'John Doe',
    email: 'john_doe_int@mail.com',
    password: '123456',
    passwordConfirmation: '123456',
  });

  describe('POST /signup', () => {
    it('should return 200 if valid data is passed', async () => {
      const httpResponse = await request(app)
        .post('/api/signup')
        .send(makeFakeUserData());

      expect(httpResponse.statusCode).toBe(200);
    });
  });
});

3. 禁用Vitest模块缓存(可选,不推荐)

若不想修改代码结构,可在Vitest配置中禁用缓存,但会降低测试速度:

// vitest.config.ts
export default defineConfig({
  test: {
    cache: false,
    // 其他配置...
  },
});

此方法仅规避缓存带来的假象,未解决异步加载未等待的核心问题,优先推荐前两种方案。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 03:23:10