Express API单元测试要点:分步指南

作者:API传播员 · 2025-11-01 · 阅读时间:6分钟

单元测试是软件开发中至关重要的一部分。它的核心是测试代码的最小单元(如函数或方法),通过这些测试,可以在代码行为偏离预期时及时发现问题,起到保护作用。

本文将为您提供一份详细的分步指南,帮助您为 Node.js Express API 编写单元测试。在阅读完本文后,您将了解单元测试的工作原理、编写方法以及如何快速定位错误。


什么是单元测试?

单元测试是指在隔离环境中测试应用程序的最小功能单元。隔离测试的关键在于确保每个单元独立运行,避免外部依赖的干扰。

Web 应用程序通常会连接数据库、调用外部服务或操作文件系统。单元测试的目标是确保这些单元在独立运行时能够按预期工作。然而,单元测试并不能替代集成测试,后者用于验证多个单元协同工作的正确性。


为什么单元测试很重要?

单元测试是 Web 应用程序测试的第一步,通常也是最基础的一步。它的意义包括:

  1. 早期发现错误:在开发周期的早期阶段编写单元测试,可以在问题扩散前及时发现。
  2. 代码文档化:单元测试可以作为代码库的文档,帮助开发者理解代码的预期行为。
  3. 支持测试驱动开发(TDD):通过先编写测试再实现功能,可以明确开发目标。

尽管单元测试无法覆盖所有场景,但它仍然是提高代码可靠性的重要工具。


快速入门指南

在开始之前,确保您已经有一个运行中的 Express.js API 项目。如果没有,可以从 GitHub 下载示例代码。以下是示例 API 的五个端点:

  1. GET /health/sync – 同步返回 "OK"。
  2. GET /health/async – 异步返回 "OK"。
  3. GET /item/:hash – 从 MongoDB 获取匹配的项。
  4. POST /item – 在 MongoDB 中创建新项。
  5. PUT /item – 更新 MongoDB 项目的 hash 值。

安装 Mocha 和 Chai

首先,安装单元测试所需的工具 Mocha 和 Chai:

npm install --save-dev mocha chai

Mocha 是一个功能强大的 JavaScript 测试框架,而 Chai 是一个断言库,提供了多种断言风格。


创建第一个测试

在项目目录中创建一个 tests 文件夹,并在其中添加一个名为 health.spec.js 的文件。以下是 /health 端点的基本测试示例:

const chai = require('chai');
const chaiHttp = require('chai-http');
const app = require('../app'); // 引入 Express 应用

chai.use(chaiHttp);
const { expect } = chai;

describe('Health API 测试', () => {
  it('应返回同步健康状态', (done) => {
    chai.request(app)
      .get('/health/sync')
      .end((err, res) => {
        expect(res).to.have.status(200);
        expect(res.text).to.equal('OK');
        done();
      });
  });
});

运行测试前,在 package.jsonscripts 部分添加以下内容:

"scripts": {
  "test": "mocha tests/**/*.spec.js"
}

运行以下命令即可执行测试:

npm test

测试异步操作

对于异步端点,可以使用以下方式测试:

it('应返回异步健康状态', async () => {
  const res = await chai.request(app).get('/health/async');
  expect(res).to.have.status(200);
  expect(res.text).to.equal('OK');
});

Mocha 支持多种处理异步操作的方法,包括 async/awaitthen 和回调。


使用钩子管理测试生命周期

Mocha 提供了以下钩子,用于在测试前后执行特定操作:

  1. before – 在所有测试运行前执行。
  2. beforeEach – 在每个测试运行前执行。
  3. after – 在所有测试运行后执行。
  4. afterEach – 在每个测试运行后执行。

示例:

before(() => {
  console.log('所有测试开始前执行');
});

after(() => {
  console.log('所有测试结束后执行');
});

模拟外部依赖

在单元测试中,避免直接调用数据库或外部服务。可以使用 Sinon 创建存根和模拟:

npm install --save-dev sinon

示例:测试 readItem 函数时,模拟数据库调用:

const sinon = require('sinon');
const mongoose = require('mongoose');

describe('readItem 测试', () => {
  let findOneStub;

  before(() => {
    findOneStub = sinon.stub(mongoose.Model, 'findOne');
  });

  after(() => {
    findOneStub.restore();
  });

  it('应返回指定的项', async () => {
    findOneStub.resolves({ hash: '123', name: '测试项' });

    const result = await readItem('123');
    expect(result.name).to.equal('测试项');
  });
});

测试覆盖率

使用 nyc 工具生成代码覆盖率报告:

npm install --save-dev nyc

package.json 中添加以下脚本:

"scripts": {
  "coverage": "nyc mocha tests/**/*.spec.js"
}

运行以下命令生成覆盖率报告:

npm run coverage

测试驱动开发(TDD)

TDD 是一种敏捷开发方法,强调在实现功能前先编写测试。其流程如下:

  1. 编写测试用例。
  2. 运行测试(初始状态下应全部失败)。
  3. 实现功能,直到所有测试通过。
  4. 重构代码并确保测试仍然通过。

TDD 的优势在于提高代码质量和开发者信心,但可能会降低开发速度。


单元测试的局限性

尽管单元测试有诸多优点,但也存在以下局限性:

  1. 无法替代集成测试和端到端测试。
  2. 难以覆盖所有场景和边界情况。
  3. 编写全面的测试可能会增加开发时间。

总结

通过本文,您已经学习了如何为 Express.js API 编写单元测试,包括基础测试、异步测试、模拟外部依赖以及生成覆盖率报告。同时,我们还介绍了测试驱动开发(TDD)的基本概念。

单元测试是提高代码质量的重要工具,但它并非万能。建议结合集成测试和端到端测试,全面提升应用程序的可靠性。


资源

原文链接: https://rrawat.com/blog/unit-test-express-api