ホームページ  >  記事  >  ウェブフロントエンド  >  Mocha と Chai を使用した NodeJS の単体テスト

Mocha と Chai を使用した NodeJS の単体テスト

Patricia Arquette
Patricia Arquetteオリジナル
2024-10-26 06:58:03625ブラウズ

Unit testing for NodeJS using Mocha and Chai
単体テストは、コードの小さな部分をチェックして正しく動作することを確認し、バグを早期に発見するため、重要です。アプリをリリースする前にこれらのテストを行うことが重要です。このガイドでは、Mocha と Chai を使用した単体テストについて説明します。

なぜモカとチャイなのか?

Mocha は、Node.js 上で実行される機能が豊富な JavaScript テスト フレームワークで、非同期テストを簡単かつ楽しいものにします。特定の順序で実行し、テスト結果を収集し、正確なレポートを提供する機能を提供します。

Chai は、任意の JavaScript テスト フレームワークで使用できる BDD/TDD アサーション ライブラリです。いくつかのインターフェイスが提供されているため、開発者は最も使いやすいアサーション スタイルを選択できます。

モカとチャイを使用する利点

  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(): すべてのテストの前に 1 回実行します

  • after(): すべてのテスト後に 1 回実行します

  • beforeEach(): 各テストの前に実行します

  • afterEach(): 各テストの後に実行します

Mocha の description() ブロックと it() ブロック

テストは、関連するテストをグループ化するための description() ブロックと、個々のテスト ケース用の 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 パターンに従う

    • 手配: テストデータと条件を設定します
    • 動作: テスト対象のコードを実行します
    • アサート: 結果を確認します
    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
    });
    

    エッジケースをテストする

    境界条件、エラー シナリオ、無効な入力、空または null 値をテストします。

    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 config でグローバル タイムアウトを設定する
    • 必要に応じて特定のテストのタイムアウトをオーバーライドする
    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 単体テスト ジェネレーターの紹介

チャイを使用したモカの手動テスト ケースを作成することは効果的ではありますが、多くの場合、いくつかの課題があります。

  1. 時間のかかる: 詳細なテスト スイートを手動で作成すると、特に大規模なコードベースの場合、かなりの時間がかかることがあります。

  2. 保守が難しい: アプリケーションが変更されると、手動テストの更新と保守がより複雑になり、エラーが発生しやすくなります。

  3. 一貫性のないカバレッジ: 開発者は、主要なパス、欠落しているエッジ ケース、または本番環境でバグを引き起こす可能性のあるエラー シナリオに重点を置く可能性があります。

  4. スキル依存: 手動テストの品質と有効性は、開発者のテスト スキルとコードベースの精通度に大きく依存します。

  5. 反復的で退屈: 複数のコンポーネントまたは関数に対して同様のテスト構造を記述するのは退屈であり、詳細への注意力が低下する可能性があります。

  6. フィードバックの遅延: 手動テストの作成に時間がかかると開発が遅れ、コードの品質と機能に関する重要なフィードバックが遅れる可能性があります。

これらの問題に取り組むために、Keploy は AI を使用してテスト プロセスを自動化および改善する ut-gen を導入しました。これは、コード セマンティクスを理解し、意味のある単体テストを作成する Meta LLM 研究論文の最初の実装です。

完全な単体テストを迅速に作成することで、単体テスト生成 (UTG) を自動化することを目的としています。これにより、反復的な手動作業の必要性が減り、手動で見逃しがちなより複雑なシナリオをカバーするためにテストを拡張することでエッジ ケースが改善され、コードベースの成長に合わせてテスト カバレッジが増加して完全なカバレッジが保証されます。

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

主な機能

  • 単体テスト生成 (UTG) の自動化: 包括的な単体テストを迅速に生成し、余分な手動作業を削減します。

  • エッジケースの改善: 手動で見逃しがちなより複雑なシナリオをカバーするために、テストの範囲を拡張および改善します。

  • テスト カバレッジの向上: コードベースが拡大するにつれて、網羅的なカバレッジを確保することが可能になります。

結論

結論として、アプリケーションの信頼性と強力さを求める開発者にとって、Mocha と Chai を使用した Node.js バックエンド テストをマスターすることは重要です。 Mocha のテスト フレームワークと Chai の明確なアサーション ライブラリを使用することで、開発者は、単純な単体テストから複雑な非同期操作に至るまで、コードの多くの部分をカバーする詳細なテスト スイートを作成できます。テストに焦点を当て続ける、明確な名前を使用する、Promise を正しく処理するなどのベスト プラクティスに従うと、テスト プロセスを大幅に改善できます。開発ワークフローでこれらのツールとテクニックを使用すると、バグを早期に発見し、コードの品質を向上させ、より安全で効率的なアプリケーションを提供できます。

FAQ

モカとチャイの違いは何ですか?両方必要ですか?

どちらもテスト ツールですが、目的は異なります。 Mocha は、(describe() ブロックと it() ブロックを使用して) テストを編成および実行するための構造を提供するテスト フレームワークです。 Chai は、結果を検証するための関数 (expect()、 should、assert など) を提供するアサーション ライブラリです。 Chai なしで Mocha を使用することもできますが、これらを一緒に使用すると、より完全で表現力豊かなテスト ソリューションが得られます。

テスト間でテストデータをセットアップおよびクリーンアップするにはどうすればよいですか?

Mocha は、テスト データを管理するためのいくつかのライフサイクル フックを提供します。

  • before(): すべてのテストの前に 1 回実行します

  • beforeEach(): 各テストの前に実行

  • afterEach(): 各テストの後に実行します

  • after(): すべてのテスト後に 1 回実行します

例:

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 (Arrange-Act-Assert) パターンに従います

  4. テストをアトミックかつ独立に保つ

  5. ソース コード構造を反映するようにテスト ファイルを整理します

例:

{
  "scripts": {
    "test": "NODE_ENV=test mocha --timeout 10000 --exit",
    "test:coverage": "nyc npm test"
  }
}

before、beforeEach、after、afterEach はどう違いますか?

「before」はすべてのテストの前に 1 回実行され、「beforeEach」は各個別のテストの前に実行され、「afterEach」は各個別のテストの後に実行され、「after」はすべてのテストが完了した後に 1 回実行されます。これらは、テスト データのセットアップとクリーンアップに役立ちます。

非同期コードをテストするにはどうすればよいですか?

テストでは async/await または return Promise を使用できます。テスト関数の前に「async」を追加し、非同期操作を呼び出すときに「await」を使用するだけです。長時間の操作には適切なタイムアウトを設定してください。

以上がMocha と Chai を使用した NodeJS の単体テストの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。