>웹 프론트엔드 >JS 튜토리얼 >Mocha와 Chai를 사용한 NodeJS 단위 테스트

Mocha와 Chai를 사용한 NodeJS 단위 테스트

Patricia Arquette
Patricia Arquette원래의
2024-10-26 06:58:03726검색

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(): 모든 테스트 전에 한 번 실행

  • after(): 모든 테스트 후에 한 번 실행

  • beforeEach(): 각 테스트 전에 실행

  • afterEach(): 각 테스트 후에 실행

Mocha의 explain() 및 it() 블록

테스트는 관련 테스트를 그룹화하는 데 explain() 블록을 사용하고 개별 테스트 사례에는 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 구성에서 전역 시간 초과 구성
    • 필요한 경우 특정 테스트에 대한 시간 초과 재정의
    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는 AI를 사용하여 테스트 프로세스를 자동화하고 개선하는 ut-gen을 도입했습니다. 이는 코드 의미를 이해하고 의미 있는 단위 테스트를 생성하는 Meta LLM 연구 논문의 첫 번째 구현입니다.

철저한 단위 테스트를 신속하게 제작하여 단위 테스트 생성(UTG)을 자동화하는 것을 목표로 합니다. 이를 통해 반복적인 수동 작업의 필요성이 줄어들고, 종종 수동으로 놓칠 수 있는 더 복잡한 시나리오를 처리하도록 테스트를 확장하여 극단적인 사례를 개선하며, 코드베이스가 커짐에 따라 전체 적용 범위를 보장하기 위해 테스트 범위가 늘어납니다.

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

주요 기능

  • UTG(단위 테스트 생성) 자동화: 포괄적인 단위 테스트를 신속하게 생성하고 중복된 수동 작업을 줄입니다.

  • 특수 사례 개선: 수동으로 놓치기 쉬운 더 복잡한 시나리오를 포괄하도록 테스트 범위를 확장하고 개선합니다.

  • 테스트 커버리지 강화: 코드베이스가 커짐에 따라 철저한 커버리지 보장이 가능해졌습니다.

결론

결론적으로 Mocha 및 Chai를 사용한 Node.js 백엔드 테스트를 마스터하는 것은 애플리케이션이 안정적이고 강력해지기를 원하는 개발자에게 중요합니다. Mocha의 테스트 프레임워크와 Chai의 명확한 어설션 라이브러리를 사용하여 개발자는 간단한 단위 테스트부터 복잡한 비동기 작업까지 코드의 많은 부분을 포괄하는 상세한 테스트 모음을 만들 수 있습니다. 테스트에 집중하고, 명확한 이름을 사용하고, 약속을 올바르게 처리하는 등의 모범 사례를 따르면 테스트 프로세스가 크게 향상됩니다. 개발 워크플로에서 이러한 도구와 기술을 사용하면 버그를 조기에 발견하고, 코드 품질을 개선하며, 보다 안전하고 효율적인 애플리케이션을 제공할 수 있습니다.

FAQ

Mocha와 Chai의 차이점은 무엇이며 둘 다 필요합니까?

둘 다 테스트 도구이기는 하지만 용도가 다릅니다. Mocha는 테스트를 구성하고 실행하기 위한 구조를 제공하는 테스트 프레임워크입니다(describe() 및 it() 블록 사용). Chai는 결과를 확인하기 위한 함수(예: Expect(), Should 및 Assert)를 제공하는 Assertion 라이브러리입니다. 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(Arrange-Act-Assert) 패턴을 따릅니다

  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 또는 return promise를 사용할 수 있습니다. 테스트 함수 앞에 'async'를 추가하고 비동기 작업을 호출할 때 'await'를 사용하세요. 더 긴 작업을 위해서는 적절한 시간 초과를 설정해야 합니다.

위 내용은 Mocha와 Chai를 사용한 NodeJS 단위 테스트의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.