首頁 > web前端 > js教程 > 主體

使用 Mocha 和 Chai 對 NodeJS 進行單元測試

Patricia Arquette
發布: 2024-10-26 06:58:03
原創
624 人瀏覽過

Unit testing for NodeJS using Mocha and Chai
單元測試很重要,因為它會檢查一小部分程式碼,以確保它們正常運作並儘早發現錯誤。在發布應用程式之前進行這些測試非常重要。本指南將涵蓋 Mocha 和 Chai 的單元測試。

為什麼選擇摩卡和柴茶?

Mocha 是一個功能豐富的 JavaScript 測試框架,運行在 Node.js 上,讓非同步測試變得簡單而愉快。它提供按特定順序執行、收集測試結果並提供準確報告的功能。

Chai 是一個 BDD/TDD 斷言函式庫,可以與任何 JavaScript 測試框架一起使用。它提供了多種接口,允許開發人員選擇他們認為最舒適的斷言樣式。

使用摩卡和柴的好處

  1. 可讀且富有表現力的斷言

    Chai 提供了與 Mocha 配合良好的不同斷言樣式和語法選項,讓您可以選擇適合您的清晰度和可讀性需求的樣式。

  2. 支援非同步測試

    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
登入後複製
登入後複製

API端點

  1. 身份驗證端點:
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 }
登入後複製
登入後複製
  1. 用戶端點:
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():每次測試後運行

Mocha 的describe() 和it() 塊

使用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 }
登入後複製
登入後複製

測試 CRUD 操作

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
    
    登入後複製
    登入後複製

    遵循 AAA 模式

    • 安排:設定測試資料和條件
    • Act:執行正在測試的程式碼
    • 斷言:驗證結果
    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);
      });
    });
    
    登入後複製

    正確處理承諾

    • 總是回傳 Promise 或使用 async/await
    • 使用正確的錯誤處理
    • 測試成功與失敗的案例
    const mongoose = require('mongoose');
    
    before(async () => {
      await mongoose.connect(process.env.MONGODB_URI_TEST);
    });
    
    after(async () => {
      await mongoose.connection.dropDatabase();
      await mongoose.connection.close();
    });
    
    登入後複製

    設定適當的超時

    • 為非同步操作設定實際的逾時
    • 在 mocha 配置中配置全域逾時
    • 在需要時覆蓋特定測試的超時
    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;
      });
    });
    
    登入後複製

介紹 Keploy 單元測試產生器

使用 chai 為 mocha 編寫手動測試案例雖然有效,但通常會遇到一些挑戰:

  1. 耗時:手動製作詳細的測試套件可能需要很多時間,尤其是對於大型程式碼庫。

  2. 難以維護:隨著應用程式的變化,更新和維護手動測試變得更加複雜且容易出錯。

  3. 覆蓋範圍不一致:開發人員可能會注意主要路徑、缺少的邊緣情況或可能導致生產中出現錯誤的錯誤場景。

  4. 取決於技能:手動測試的品質和有效性在很大程度上取決於開發人員的測試技能和對程式碼庫的熟悉程度。

  5. 重複且乏味:為多個組件或函數編寫類似的測試結構可能很無聊,可能會導致對細節的關注較少。

  6. 延遲回饋:編寫手動測試所需的時間會減慢開發速度,延遲對程式碼品質和功能的重要回饋。

為了解決這些問題,Keploy 推出了 ut-gen,它使用 AI 來自動化和改進測試過程,這是 Meta LLM 研究論文的第一個實現,可以理解程式碼語義並創建有意義的單元測試。

它的目標是透過快速產生徹底的單元測試來自動化單元測試產生(UTG)。這減少了對重複性手動工作的需求,透過擴展測試以覆蓋經常手動錯過的更複雜的場景來改善邊緣情況,並增加測試覆蓋率以確保隨著程式碼庫的增長而完全覆蓋。

%[https://marketplace.visualstudio.com/items?itemName=Keploy.keployio]

主要特點

  • 自動化單元測試產生(UTG):快速產生全面的單元測試並減少冗餘的手動工作。

  • 改進邊緣情況:擴展和改進測試範圍,以涵蓋手動經常錯過的更複雜的場景。

  • 提高測試覆蓋率:隨著程式碼庫的成長,確保詳盡的覆蓋率變得可行。

結論

總之,掌握使用 Mocha 和 Chai 進行 Node.js 後端測試對於希望其應用程式可靠且強大的開發人員來說非常重要。透過使用 Mocha 的測試框架和 Chai 的清晰斷言庫,開發人員可以創建詳細的測試套件,涵蓋其程式碼的許多部分,從簡單的單元測試到複雜的非同步操作。遵循最佳實踐(例如保持測試重點、使用清晰的名稱和正確處理承諾)可以極大地改進您的測試過程。透過在開發工作流程中使用這些工具和技術,您可以及早發現錯誤、提高程式碼品質並交付更安全、更有效率的應用程式。

常見問題解答

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
登入後複製
登入後複製
登入後複製

我應該如何建立我的測試以便更好地組織和維護?

  1. 使用describe()區塊將相關檢定分組

  2. 使用清楚說明預期行為的描述性測試名稱

  3. 在每個測試中遵循 AAA(安排-執行-斷言)模式

  4. 保持測試原子性和獨立性

  5. 組織測試文件以反映原始碼結構

範例:

{
  "scripts": {
    "test": "NODE_ENV=test mocha --timeout 10000 --exit",
    "test:coverage": "nyc npm test"
  }
}
登入後複製
登入後複製
登入後複製

before、beforeEach、after 和 afterEach 之間有什麼區別?

'before' 在所有測試之前運行一次,'beforeEach' 在每個單獨的測試之前運行,'afterEach' 在每個單獨的測試之後運行,'after' 在所有測試完成後運行一次。這些對於設定和清理測試資料很有用。

如何測試異步程式碼?

您可以在測試中使用非同步/等待或返回承諾。只需在測試函數之前新增“async”,並在呼叫非同步操作時使用“await”即可。確保為較長的操作設定適當的逾時。

以上是使用 Mocha 和 Chai 對 NodeJS 進行單元測試的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!