ویژگی تصویر

تست نویسی با Jest در Node.js — راهنمای کامل

  /  Node.js   /  تست نویسی با Jest در Node.js
بنر تبلیغاتی الف
NodeJS - Node.js

Jest یکی از محبوب‌ترین فریم‌ورک‌های تست برای اکوسیستم JavaScript و مخصوصاً Node.js است. در این مقاله به صورت عملی و کاربردی مفهوم تست‌های واحد و یکپارچه، تنظیم محیط، تست‌های همگام و ناهمگام، موک‌سازی (mocking)، تست‌های Snapshot و نکات حرفه‌ای برای نگهداری تست‌ها را بررسی می‌کنیم.

چرا Jest برای Node.js؟

  • راه‌اندازی ساده و صفر-پیکربندی برای بسیاری از پروژه‌ها.
  • پشتیبانی از Mocking داخلی و Coverage گزارش.
  • سرعت خوب و اجرا در حالت watch برای توسعه سریع.
  • سازگاری با TypeScript، Babel و ابزارهای CI.

نصب و پیکربندی اولیه

npm init -y
npm install --save-dev jest
# اگر از TypeScript استفاده می‌کنید:
npm install --save-dev ts-jest @types/jest

در فایل package.json کافی است script مربوط به تست را اضافه کنید:

{
  "scripts": {
    "test": "jest --coverage"
  }
}

این تنظیم ساده اجازه می‌دهد با اجرای npm test تست‌ها اجرا شده و گزارش پوشش کد (coverage) تولید شود.

نوشتن اولین تست واحد

// sum.js
function sum(a, b) {
  return a + b;
}
module.exports = sum;

// sum.test.js
const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

در این مثال تابع ساده‌ای داریم و یک فایل تست که با تابع global jest یعنی test تعریف می‌شود. matcher های Jest مانند toBe برای مقایسه دقیق استفاده می‌شوند.

تست توابع ناهمگام (Promises / async-await)

// fetchData.js
const fetch = require('node-fetch');

async function fetchData(url) {
  const res = await fetch(url);
  return res.json();
}
module.exports = fetchData;

// fetchData.test.js
const fetchData = require('./fetchData');

test('fetches data from API', async () => {
  const data = await fetchData('https://jsonplaceholder.typicode.com/todos/1');
  expect(data).toHaveProperty('id', 1);
});

در تست بالا از async/await استفاده شده تا فراخوانی ناهمگام به شکل ساده تست شود. در تست‌های مبتنی بر Promise می‌توان از return یا resolves/rejects نیز استفاده کرد.

موک‌سازی: از توابع ساده تا ماژول‌ها

// db.js
module.exports = {
  getUser: async (id) => {
    // تماس با دیتابیس واقعی
  }
};

// userService.js
const db = require('./db');
async function getUserName(id) {
  const user = await db.getUser(id);
  return user.name;
}
module.exports = getUserName;

// userService.test.js
jest.mock('./db');
const db = require('./db');
const getUserName = require('./userService');

test('returns user name from mocked db', async () => {
  db.getUser.mockResolvedValue({ id: 1, name: 'Ali' });
  const name = await getUserName(1);
  expect(name).toBe('Ali');
});

با jest.mock یک ماژول را جایگزین می‌کنیم و رفتار توابعش را کنترل می‌کنیم. این کار باعث سریع‌تر و ایزوله‌تر شدن تست‌ها می‌شود و وابستگی به دیتابیس یا سرویس خارجی را حذف می‌کند.

تست مسیرهای HTTP با Supertest

// app.js
const express = require('express');
const app = express();
app.get('/ping', (req, res) => res.json({ message: 'pong' }));
module.exports = app;

// app.test.js
const request = require('supertest');
const app = require('./app');

test('GET /ping returns pong', async () => {
  const res = await request(app).get('/ping');
  expect(res.statusCode).toBe(200);
  expect(res.body).toEqual({ message: 'pong' });
});

برای تست APIهای Express، supertest ابزار مناسبی است که امکان اجرای درخواست و بررسی پاسخ بدون باز کردن پورت واقعی را می‌دهد.

تست Snapshot

// renderer.js
function renderUser(user) {
  return `User: ${user.name} (${user.id})`;
}
module.exports = renderUser;

// renderer.test.js
const renderUser = require('./renderer');

test('renders user snapshot', () => {
  const output = renderUser({ id: 1, name: 'Sara' });
  expect(output).toMatchSnapshot();
});

Snapshot برای تست ساختار خروجی (به‌خصوص در ری‌اکت یا خروجی‌های رشته‌ای بزرگ) مفید است. در اولین اجرا فایل snapshot ایجاد می‌شود و اجرای بعدی اختلاف‌ها را نشان می‌دهد.

گزارش پوشش کد (Coverage)

اجرای jest با فلگ –coverage یک گزارش پوشش تولید می‌کند. می‌توانید آن را در CI بگذارید و حداقل پوشش مورد نیاز را تعریف کنید.

ابزارهدف
Jestاجرای تست‌ها، mocking، snapshot
Supertestتست endpoint های HTTP

نکات حرفه‌ای و بهترین رویه‌ها

  • هر تست باید مستقل باشد؛ از داده‌های سراسری مشترک خودداری کنید.
  • فایل‌های تست را با پسوند .test.js یا .spec.js نام‌گذاری کنید و ساختار پوشه‌ای منظمی داشته باشید.
  • از mock کردن external API و دیتابیس استفاده کنید تا تست‌ها سریع و قابل پیش‌بینی بمانند.
  • برای تست‌های وقتی-حسگر (time-sensitive) از jest.useFakeTimers() استفاده کنید تا تایمرها قابل کنترل شوند.
  • در CI، اجرای coverage و fail کردن pipeline در صورت کمتر بودن از حد آستانه می‌تواند کیفیت را تضمین کند.

مثال پیشرفته: استفاده از timers موک‌شده

// timer.js
function delayedHello(cb) {
  setTimeout(() => {
    cb('hello');
  }, 1000);
}
module.exports = delayedHello;

// timer.test.js
const delayedHello = require('./timer');
jest.useFakeTimers();

test('calls callback after 1 second', () => {
  const cb = jest.fn();
  delayedHello(cb);

  expect(cb).not.toBeCalled();
  jest.advanceTimersByTime(1000);
  expect(cb).toBeCalledWith('hello');
});

در این تست تایمرها را موک می‌کنیم تا بدون انتظار واقعی بتوانیم رفتار تابع را بررسی کنیم. این الگو در تست زمان‌بندی و بک‌آف‌ها بسیار مفید است.

سازماندهی و نگهداری تست‌ها

به‌مرور زمان تعداد تست‌ها زیاد می‌شود. برخی توصیه‌ها:

  • تست‌های کند را جدا کنید و تنها در حالت CI یا با فلگ خاص اجرا کنید.
  • از fixture ها برای مقداردهی اولیه استفاده کنید و setup/teardown را در beforeEach/afterEach مدیریت کنید.
  • خطاهای ناپایدار (flaky tests) را به سرعت اصلاح کنید، زیرا اعتماد به مجموعه تست کاهش می‌یابد.

جمع‌بندی و منابع

Jest یک ابزار کامل برای تست در Node.js است که با ابزارهای دیگر به‌خوبی ترکیب می‌شود. با استفاده از Mocking، تست‌های ناهمگام، Snapshot و Coverage می‌توانید یک مجموعه تست قابل‌اعتماد و سریع بسازید. برای یادگیری بیشتر مستندات رسمی Jest و مثال‌های پیشرفته را مطالعه کنید.

اگر بخش خاصی از پروژه‌تان را می‌خواهید با Jest تست‌کنید (مثلاً Express، TypeScript یا microservices)، می‌توانم نمونه‌های اختصاصی و تنظیمات CI برای شما آماده کنم.

آیا این مطلب برای شما مفید بود ؟

خیر
بله
موضوعات شما در انجمن: