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); }); });
根因分析
- 异步路由加载未等待完成:路由加载函数中使用
files.map(async (file) => { ... }),但map不会等待异步函数执行完成,直接返回Promise数组,而整个路由加载函数是同步的,没有等待所有Promise resolve。导致测试启动时路由还未挂载到Express实例,首次请求触发404。 - 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
相关产品推荐
相关产品推荐

