单元测试很重要,因为它会检查一小部分代码,以确保它们正常工作并尽早发现错误。在发布应用程序之前进行这些测试非常重要。本指南将涵盖 Mocha 和 Chai 的单元测试。
Mocha 是一个功能丰富的 JavaScript 测试框架,运行在 Node.js 上,使异步测试变得简单而愉快。它提供按特定顺序执行、收集测试结果并提供准确报告的功能。
Chai 是一个 BDD/TDD 断言库,可以与任何 JavaScript 测试框架一起使用。它提供了多种接口,允许开发人员选择他们认为最舒服的断言样式。
可读且富有表现力的断言
Chai 提供了与 Mocha 配合良好的不同断言样式和语法选项,允许您选择适合您的清晰度和可读性需求的样式。
支持异步测试
Mocha 可以轻松处理异步测试,让您可以在 Node.js 应用程序中测试异步代码,而无需额外的库或复杂的设置。
首先,让我们建立一个新的 Node.js 项目并安装必要的依赖项:
mkdir auth-api-testing cd auth-api-testing npm init -y # Install production dependencies npm install express jsonwebtoken mongoose bcryptjs dotenv # Install development dependencies npm install --save-dev mocha chai chai-http supertest nyc
将以下脚本添加到您的package.json:
{ "scripts": { "test": "NODE_ENV=test mocha --timeout 10000 --exit", "test:coverage": "nyc npm test" } }
在深入测试之前,让我们先了解一下我们将要测试的应用程序。我们正在构建一个简单但安全的身份验证 API,具有以下功能。
src/ ├── models/ │ └── user.model.js # User schema and model ├── routes/ │ ├── auth.routes.js # Authentication routes │ └── user.routes.js # User management routes ├── middleware/ │ ├── auth.middleware.js # JWT verification middleware │ └── validate.js # Request validation middleware ├── controllers/ │ ├── auth.controller.js # Authentication logic │ └── user.controller.js # User management logic └── app.js # Express application setup
POST /api/auth/register - Registers new user - Accepts: { email, password, name } - Returns: { token, user } POST /api/auth/login - Authenticates existing user - Accepts: { email, password } - Returns: { token, user }
GET /api/users/profile - Gets current user profile - Requires: JWT Authentication - Returns: User object PUT /api/users/profile - Updates user profile - Requires: JWT Authentication - Accepts: { name, email } - Returns: Updated user object
为特定于测试的配置创建 .env.test 文件:
PORT=3001 MONGODB_URI=mongodb://localhost:27017/auth-api-test JWT_SECRET=your-test-secret-key
让我们创建第一个测试文件 test/auth.test.js
const chai = require('chai'); const chaiHttp = require('chai-http'); const app = require('../src/app'); const User = require('../src/models/user.model'); chai.use(chaiHttp); const expect = chai.expect; describe('Auth API Tests', () => { // Runs before all tests before(async () => { await User.deleteMany({}); }); // Runs after each test afterEach(async () => { await User.deleteMany({}); }); // Test suites will go here });
Mocha 提供了几个用于测试设置和清理的钩子:
before():在所有测试之前运行一次
after():在所有测试后运行一次
beforeEach():在每次测试之前运行
afterEach():每次测试后运行
使用describe()块来组织相关测试,并使用it()块来组织单个测试用例:
mkdir auth-api-testing cd auth-api-testing npm init -y # Install production dependencies npm install express jsonwebtoken mongoose bcryptjs dotenv # Install development dependencies npm install --save-dev mocha chai chai-http supertest nyc
{ "scripts": { "test": "NODE_ENV=test mocha --timeout 10000 --exit", "test:coverage": "nyc npm test" } }
src/ ├── models/ │ └── user.model.js # User schema and model ├── routes/ │ ├── auth.routes.js # Authentication routes │ └── user.routes.js # User management routes ├── middleware/ │ ├── auth.middleware.js # JWT verification middleware │ └── validate.js # Request validation middleware ├── controllers/ │ ├── auth.controller.js # Authentication logic │ └── user.controller.js # User management logic └── app.js # Express application setup
POST /api/auth/register - Registers new user - Accepts: { email, password, name } - Returns: { token, user } POST /api/auth/login - Authenticates existing user - Accepts: { email, password } - Returns: { token, user }
GET /api/users/profile - Gets current user profile - Requires: JWT Authentication - Returns: User object PUT /api/users/profile - Updates user profile - Requires: JWT Authentication - Accepts: { name, email } - Returns: Updated user object
每个测试应该独立,不依赖于其他测试
测试应该能够以任何顺序运行
使用 before、after、beforeEach 和 afterEach 挂钩进行正确的设置和清理
PORT=3001 MONGODB_URI=mongodb://localhost:27017/auth-api-test JWT_SECRET=your-test-secret-key
const chai = require('chai'); const chaiHttp = require('chai-http'); const app = require('../src/app'); const User = require('../src/models/user.model'); chai.use(chaiHttp); const expect = chai.expect; describe('Auth API Tests', () => { // Runs before all tests before(async () => { await User.deleteMany({}); }); // Runs after each test afterEach(async () => { await User.deleteMany({}); }); // Test suites will go here });
测试边界条件、错误场景、无效输入以及空值或空值。
describe('Auth API Tests', () => { describe('POST /api/auth/register', () => { it('should register a new user successfully', async () => { // Test implementation }); it('should return error when email already exists', async () => { // Test implementation }); }); });
测试名称应清楚地描述正在测试的场景,遵循一致的命名约定,并包含预期的行为。
describe('POST /api/auth/register', () => { it('should register a new user successfully', async () => { const res = await chai .request(app) .post('/api/auth/register') .send({ email: 'test@example.com', password: 'Password123!', name: 'Test User' }); expect(res).to.have.status(201); expect(res.body).to.have.property('token'); expect(res.body).to.have.property('user'); expect(res.body.user).to.have.property('email', 'test@example.com'); }); it('should return 400 when email already exists', async () => { // First create a user await chai .request(app) .post('/api/auth/register') .send({ email: 'test@example.com', password: 'Password123!', name: 'Test User' }); // Try to create another user with same email const res = await chai .request(app) .post('/api/auth/register') .send({ email: 'test@example.com', password: 'Password123!', name: 'Test User 2' }); expect(res).to.have.status(400); expect(res.body).to.have.property('error'); }); });
describe('Protected Routes', () => { let token; let userId; beforeEach(async () => { // Create a test user and get token const res = await chai .request(app) .post('/api/auth/register') .send({ email: 'test@example.com', password: 'Password123!', name: 'Test User' }); token = res.body.token; userId = res.body.user._id; }); it('should get user profile with valid token', async () => { const res = await chai .request(app) .get('/api/users/profile') .set('Authorization', `Bearer ${token}`); expect(res).to.have.status(200); expect(res.body).to.have.property('email', 'test@example.com'); }); it('should return 401 with invalid token', async () => { const res = await chai .request(app) .get('/api/users/profile') .set('Authorization', 'Bearer invalid-token'); expect(res).to.have.status(401); }); });
const mongoose = require('mongoose'); before(async () => { await mongoose.connect(process.env.MONGODB_URI_TEST); }); after(async () => { await mongoose.connection.dropDatabase(); await mongoose.connection.close(); });
describe('User CRUD Operations', () => { it('should update user profile', async () => { const res = await chai .request(app) .put(`/api/users/${userId}`) .set('Authorization', `Bearer ${token}`) .send({ name: 'Updated Name' }); expect(res).to.have.status(200); expect(res.body).to.have.property('name', 'Updated Name'); }); it('should delete user account', async () => { const res = await chai .request(app) .delete(`/api/users/${userId}`) .set('Authorization', `Bearer ${token}`); expect(res).to.have.status(200); // Verify user is deleted const user = await User.findById(userId); expect(user).to.be.null; }); });
使用 chai 为 mocha 编写手动测试用例虽然有效,但通常会遇到一些挑战:
耗时:手动制作详细的测试套件可能需要很多时间,尤其是对于大型代码库。
难以维护:随着应用程序的变化,更新和维护手动测试变得更加复杂并且容易出错。
覆盖范围不一致:开发人员可能会关注主要路径、缺失的边缘情况或可能导致生产中出现错误的错误场景。
取决于技能:手动测试的质量和有效性在很大程度上取决于开发人员的测试技能和对代码库的熟悉程度。
重复且乏味:为多个组件或函数编写类似的测试结构可能很无聊,可能会导致对细节的关注较少。
延迟反馈:编写手动测试所需的时间会减慢开发速度,延迟对代码质量和功能的重要反馈。
为了解决这些问题,Keploy 推出了 ut-gen,它使用 AI 来自动化和改进测试过程,这是 Meta LLM 研究论文的第一个实现,可以理解代码语义并创建有意义的单元测试。
它的目标是通过快速生成彻底的单元测试来自动化单元测试生成(UTG)。这减少了对重复性手动工作的需求,通过扩展测试以覆盖经常手动错过的更复杂的场景来改善边缘情况,并增加测试覆盖率以确保随着代码库的增长而完全覆盖。
%[https://marketplace.visualstudio.com/items?itemName=Keploy.keployio]
自动化单元测试生成(UTG):快速生成全面的单元测试并减少冗余的手动工作。
改进边缘情况:扩展和改进测试范围,以涵盖手动经常错过的更复杂的场景。
提高测试覆盖率:随着代码库的增长,确保详尽的覆盖率变得可行。
总之,掌握使用 Mocha 和 Chai 进行 Node.js 后端测试对于希望其应用程序可靠且强大的开发人员来说非常重要。通过使用 Mocha 的测试框架和 Chai 的清晰断言库,开发人员可以创建详细的测试套件,涵盖其代码的许多部分,从简单的单元测试到复杂的异步操作。遵循最佳实践(例如保持测试重点、使用清晰的名称和正确处理承诺)可以极大地改进您的测试过程。通过在开发工作流程中使用这些工具和技术,您可以及早发现错误、提高代码质量并交付更安全、更高效的应用程序。
虽然两者都是测试工具,但它们的用途不同。 Mocha 是一个测试框架,提供组织和运行测试的结构(使用describe() 和it() 块)。 Chai 是一个断言库,提供用于验证结果的函数(如expect()、should 和assert)。虽然您可以在没有 Chai 的情况下使用 Mocha,但将它们一起使用可以为您提供更完整且更具表现力的测试解决方案。
Mocha 提供了多个生命周期挂钩来管理测试数据:
before():在所有测试之前运行一次
beforeEach():在每次测试之前运行
afterEach():每次测试后运行
after():在所有测试后运行一次
示例:
mkdir auth-api-testing cd auth-api-testing npm init -y # Install production dependencies npm install express jsonwebtoken mongoose bcryptjs dotenv # Install development dependencies npm install --save-dev mocha chai chai-http supertest nyc
使用describe()块对相关测试进行分组
使用清楚说明预期行为的描述性测试名称
在每个测试中遵循 AAA(安排-执行-断言)模式
保持测试原子性和独立性
组织测试文件以反映源代码结构
示例:
{ "scripts": { "test": "NODE_ENV=test mocha --timeout 10000 --exit", "test:coverage": "nyc npm test" } }
'before' 在所有测试之前运行一次,'beforeEach' 在每个单独的测试之前运行,'afterEach' 在每个单独的测试之后运行,'after' 在所有测试完成后运行一次。这些对于设置和清理测试数据很有用。
您可以在测试中使用异步/等待或返回承诺。只需在测试函数之前添加“async”,并在调用异步操作时使用“await”即可。确保为较长的操作设置适当的超时。
以上是使用 Mocha 和 Chai 对 NodeJS 进行单元测试的详细内容。更多信息请关注PHP中文网其他相关文章!