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

解决多TypeScript API批量测试中的服务器启动异常问题

批量API测试时服务器关闭错误的解决方案

问题描述

测试Restaurant和Employee两个API的POST、GET方法,API本身运行正常。测试文件分别存放在test\restaurant\index.test.ts和test\employee\index.test.ts,单独运行单个测试文件时全部通过,但执行批量测试脚本:

npm run knex:latest && npm run test:compile && npm run test:script

时部分测试失败,报错:

Error [ERR_SERVER_NOT_RUNNING]: Server is not running.

错误原因

  1. 进程提前终止:每个测试文件的after钩子中调用了process.exit(0),第一个测试套件执行完毕后直接终止整个Node进程,导致第二个测试套件无法正常启动服务器。
  2. 无状态检查的服务器关闭:调用server.close()前未检查服务器是否处于运行状态,触发未运行实例的关闭错误。
  3. 模块缓存复用:多次导入../../src/index时,Node.js会复用模块缓存,可能拿到已经关闭的服务器实例。
  4. 接口路径错误:Employee测试的GET接口路径错误(原代码使用/api/v1/offerings,实际应为/api/v1/employees)。

修复方案

  1. 移除所有测试文件after钩子中的process.exit()调用,让测试框架自动管理测试流程。
  2. 给服务器关闭逻辑增加状态检查,仅当服务器处于监听状态时才执行关闭操作。
  3. 在每个测试的before钩子中清理模块缓存,确保每次导入src/index都能获取全新的服务器实例。
  4. 修正Employee测试中的GET接口路径错误。

修改后的测试代码

test\restaurant\index.test.ts

import { before, describe, it, after } from 'node:test';
import assert from 'node:assert';
import { promisify } from 'node:util';
import { Server } from 'http';

describe('Restaurant Test Suite', () => {
  let serverTest: Server;
  let isServerListening = false;

  before(async () => {
    // 清理模块缓存,确保每次获取新的服务器实例
    delete require.cache[require.resolve('../../src/index')];
    const { server } = await import('../../src/index');
    serverTest = server;
    // 监听服务器启动事件,标记运行状态
    serverTest.on('listening', () => {
      isServerListening = true;
    });
  });

  after(async () => {
    try {
      if (isServerListening) {
        await promisify(serverTest.close.bind(serverTest))();
      }
    } catch (err) {
      console.error('关闭服务器时出错:', err);
    }
  });

  it('Restaurant Post', async (t) => {
    const testPort = process.env.PORT;
    const testServerAddress = `http://localhost:${testPort}/api/v1/restaurant`;

    await t.test('it should create a restaurant', async () => {
      const data = {
        name: 'Test Name',
        active: false,
        description: 'Test Description',
      };
      const request = await fetch(testServerAddress, {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });

      assert.strictEqual(request.status, 200);
      interface Result {
        message: string;
      }
      const result = await (<Promise<Result>>request.json());
      assert.deepStrictEqual(
        result.message,
        'Restaurant created successfully',
        '返回正确的创建成功消息',
      );
    });
  });

  it('Get Restaurants', async (t) => {
    const testPort = process.env.PORT;
    const testServerAddress = `http://localhost:${testPort}/api/v1/restaurants`;

    await t.test('it should get restaurants', async () => {
      const request = await fetch(testServerAddress, {
        method: 'GET',
      });

      assert.strictEqual(request.status, 200);
      interface Data {
        id: string;
      }
      interface Result {
        data: Array<Data>;
      }
      const result = await (<Promise<Result>>request.json());
      assert.ok(result.data[0].id.length > 30, '返回的餐厅UUID格式正确');
    });
  });
});

test\employee\index.test.ts

import { before, describe, it, after } from 'node:test';
import assert from 'node:assert';
import { promisify } from 'node:util';
import { Server } from 'http';

describe('Employee Test Suite', () => {
  let serverTest: Server;
  let isServerListening = false;

  before(async () => {
    // 清理模块缓存,确保每次获取新的服务器实例
    delete require.cache[require.resolve('../../src/index')];
    const { server } = await import('../../src/index');
    serverTest = server;
    // 监听服务器启动事件,标记运行状态
    serverTest.on('listening', () => {
      isServerListening = true;
    });
  });

  after(async () => {
    try {
      if (isServerListening) {
        await promisify(serverTest.close.bind(serverTest))();
      }
    } catch (err) {
      console.error('关闭服务器时出错:', err);
    }
  });

  it('Employee Post', async (t) => {
    const testPort = process.env.PORT;
    const testServerAddress = `http://localhost:${testPort}/api/v1/employee`;

    await t.test('it should create a employee', async () => {
      const data = {
        name: 'Test Employee Name',
        employee_availability: []
      };
      const request = await fetch(testServerAddress, {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });

      assert.strictEqual(request.status, 200);
      interface Result {
        message: string;
      }
      const result = await (<Promise<Result>>request.json());
      assert.deepStrictEqual(
        result.message,
        'Employee created successfully',
        '返回正确的创建成功消息',
      );
    });
  });

  it('Get Employees', async (t) => {
    const testPort = process.env.PORT;
    // 修正接口路径错误
    const testServerAddress = `http://localhost:${testPort}/api/v1/employees`;

    await t.test('it should get employees', async () => {
      const request = await fetch(testServerAddress, {
        method: 'GET',
      });

      assert.strictEqual(request.status, 200);
      interface Data {
        id: string;
      }
      interface Result {
        data: Array<Data>;
      }
      const result = await (<Promise<Result>>request.json());
      assert.ok(result.data[0].id.length > 30, '返回的员工UUID格式正确');
    });
  });
});

验证步骤

  1. 保存修改后的两个测试文件。
  2. 执行批量测试脚本:
npm run knex:latest && npm run test:compile && npm run test:script
  1. 确认所有测试用例均通过,无ERR_SERVER_NOT_RUNNING错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 18:42:05