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

API测试全部通过但运行test.js时仍出现超时退出错误

问题

所有API单元测试用例均已通过,但运行test.js时仍收到错误提示:

✘ Timed out while running tests
Failed to exit when running test.js

尝试过设置20秒全局超时、添加sinon.restore()恢复stub、给每个测试用例设置10秒超时,均无效果。

相关代码

test.js

const test = require('ava');
const request = require('supertest');
const sinon = require('sinon');
const proxyquire = require('proxyquire');

// Mock database operations
const dbMock = {
  getBikes: sinon.stub().callsFake((callback) => callback(null, [{ id: 1, name: 'Bike 1' }])),
  getUsers: sinon.stub().callsFake((callback) => callback(null, [{ id: 1, name: 'User 1' }])),
  getBikeUsage: sinon.stub().callsFake((callback) => callback(null, [{ id: 1, bikeId: 1, userId: 1 }])),
  saveBikeUsage: sinon.stub().callsFake((bikeUsage, callback) => callback(null, { insertId: 1 })),
  updateBikeUsageStatus: sinon.stub().callsFake((bikeId, status, callback) => callback(null, {})),
  unassignBike: sinon.stub().callsFake((bikeId, endTime, callback) => callback(null, {})),
  updateBikeDetails: sinon.stub().callsFake((id, details, callback) => callback(null, {})),
  updateUserDetails: sinon.stub().callsFake((id, name, callback) => callback(null, {})),
  addUser: sinon.stub().callsFake((name, callback) => callback(null, { insertId: 1 })),
  addBike: sinon.stub().callsFake((bike, callback) => callback(null, { insertId: 1 })),
  getUserById: sinon.stub().callsFake((userId, callback) => callback(null, [{ id: userId, name: 'User 1' }])),
  getUserHistory: sinon.stub().callsFake((userId, callback) => callback(null, [{ id: 1, bikeId: 1, userId: userId }])),
  addCommentToBike: sinon.stub().callsFake((bikeId, comment, callback) => callback(null, {})),
};

// Proxyquire to replace './db' with our mock
const app = proxyquire('./server', { './db': dbMock });

test.afterEach.always(() => {
  sinon.restore(); // Restore all stubs and spies after each test
});

// Test for GET /bikes endpoint
test('GET /bikes returns list of bikes', async (t) => {
  const res = await request(app).get('/bikes');
  t.is(res.status, 200);
  t.true(Array.isArray(res.body));
  t.true(res.body.length > 0);
});

// 其余测试用例省略(与原代码一致)

db.js

const mysql = require('mysql2');

// Create a connection pool
const pool = mysql.createPool({
  host: 'xxxx',
  user: 'xxxx',
  password: 'xxxx',
  database: 'xxxx',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

module.exports = {
  getBikes: (callback) => {
    pool.query('SELECT * FROM Bikes', callback);
  },
  // 其余数据库方法省略(与原代码一致)
};

测试结果

flinkregi_backend@1.0.0 test
ava

Server running on port 3000
  ✔ GET /bikes returns list of bikes (146ms)
  ✔ GET /users returns list of users (145ms)
  ✔ GET /bike-usage returns list of bike usage history (144ms)
  ✔ POST /bike-usage saves bike usage data (143ms)
  ✔ POST /bike-unassign unassigns bike and updates end time (142ms)
  ✔ PUT /bikes/:id updates bike details (139ms)
  ✔ PUT /users/:id updates user details (137ms)
  ✔ POST /users adds new user (138ms)
  ✔ POST /bikes adds new bike (137ms)
  ✔ GET /users/:id returns user details for valid ID (136ms)
  ✔ GET /user-history/:userId returns user history for valid user ID (136ms)
  ✔ POST /bikes/:bikeId/comments adds comment to bike (137ms)
  
  ✘ Timed out while running tests

  Failed to exit when running test.js

  ─

  12 tests passed
解决方案

1. 修复服务器启动逻辑

你的server.js大概率直接调用了app.listen(3000),导致测试时服务器持续运行,进程无法退出。修改server.js,让服务器只在直接运行时启动:

// server.js
const express = require('express');
const app = express();

// 中间件、路由配置...

// 仅在直接执行server.js时启动服务器,测试时由supertest处理
if (require.main === module) {
  app.listen(3000, () => {
    console.log('Server running on port 3000');
  });
}

module.exports = app;

2. 强制关闭数据库连接池(可选)

即使使用了mock,若测试中意外加载了真实的db.js,连接池会保持活跃导致进程不退出。可在测试结束后添加清理逻辑:

// 在test.js中添加全局后置钩子
test.after.always(() => {
  try {
    const realDb = require('./db');
    if (realDb.pool) {
      realDb.pool.end(); // 关闭连接池
    }
  } catch (e) {
    // 忽略错误,因为mock已替换真实db
  }
});

3. 重置Sinon mock状态

将mock创建移到beforeEach中,确保每次测试使用全新的mock,避免残留状态:

let app;

test.beforeEach(() => {
  // 每次测试重新创建mock
  const dbMock = {
    getBikes: sinon.stub().callsFake((callback) => callback(null, [{ id: 1, name: 'Bike 1' }])),
    // 其余mock方法与原代码一致
  };
  app = proxyquire('./server', { './db': dbMock });
});

test.afterEach.always(() => {
  sinon.restore();
});

4. 调整AVA全局超时(兜底方案)

若以上方法无效,可在package.json中配置AVA全局超时:

{
  "ava": {
    "timeout": "30s",
    "failFast": false
  }
}
原因分析

进程无法退出的核心是存在未关闭的活跃资源:

  • HTTP服务器:app.listen()会持有端口监听,保持事件循环活跃
  • 数据库连接池:mysql2连接池默认不会自动关闭,持有连接阻止进程退出
  • 残留mock状态:若mock未完全重置,可能残留异步任务或资源引用

内容的提问来源于stack exchange,提问作者Giel v Bussel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 20:14:55