單元測試很重要,因為它會檢查一小部分程式碼,以確保它們正常運作並儘早發現錯誤。在發布應用程式之前進行這些測試非常重要。本指南將涵蓋 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中文網其他相關文章!