تستنویسی در جاوااسکریپت دیگر یک «گزینه لوکس» نیست؛ پیششرطی برای تولید نرمافزار قابلاعتماد، قابلتوسعه و قابلنگهداری است. در پروژههای واقعی، هر تغییر کوچک، از بازنویسی یک تابع تا افزودن یک قابلیت، میتواند رفتارهای پیشبینینشده ایجاد کند. تستهای خودکار نقش تور ایمنی را دارند: خطاها را زود تشخیص میدهند، از پسرفت (regression) جلوگیری میکنند و سرعت تیم را هنگام ریفکتور بالا میبرند.
در زیستبوم جاوااسکریپت، Jest و Testing Library ترکیبی استاندارد برای تست کُد و رابط کاربری هستند. Jest یک test runner همهکاره است: اجرای تستها، گزارشگیری، ماک و اسپای، تایمرهای ساختگی، و پشتیبانی عالی از تایپ اسکریپت. در مقابل، Testing Library (و زیرمجموعهی آن React Testing Library) بر تست رفتار و تجربهی کاربر تمرکز دارد: بهجای چککردن جزییات پیادهسازی، با کوئریهای دسترسپذیر (role/name/label) سراغ عناصر میرود و تعامل واقعی کاربر (کلیک، تایپ، کیبورد) را شبیهسازی میکند.
تفاوت رویکردها مهم است: با Jest میتوان منطق خالص (توابع، ماژولها) را سریع و ایزوله آزمود؛ با Testing Library میتوان اطمینان یافت که از منظر کاربر، رابط شما همان کاری را میکند که باید بکند. کنار هم، این دو ابزار از واحد (Unit) تا ادغام (Integration) در لایهی فرانتاند را پوشش میدهند و در صورت نیاز میتوان آنها را در کنار تستهای End-to-End تکمیل کرد.
هدف این راهنما ایجاد یک نقشهی راه عملی است تا بتوانید از صفر، تستنویسی را به پروژههای واقعی اضافه کنید، چه یک کتابخانهی جاوااسکریپتی داشته باشید، چه یک اپ React با جریان داده پیچیده. تلاش میشود مثالها حداقلی، تکرارپذیر و مستقل از فریمورک باشند؛ اما در جاهایی که لازم است، به React Testing Library ارجاع داده میشود.
پیشنیازها: آشنایی پایه با Node.js و npm/PNPM/Yarn، درک مفاهیم ماژولها در جاوااسکریپت، و آشنایی مقدماتی با DOM یا یک کتابخانه UI (مثل React). اگر از TypeScript استفاده میکنید، پیکربندی آن را نیز پوشش خواهیم داد.
نقشهی مفهومی تستنویسی در جاوااسکریپت
پیش از آن که اولین خط تست را بنویسید یا Jest را نصب کنید، لازم است تصویر روشنی از نوع تستهایی که قرار است بنویسید داشته باشید. انتخاب نادرست یا بیبرنامه پیشرفتن، معمولاً به تولید تستهایی منجر میشود که یا بیش از حد شکنندهاند یا ارزش عملی کمی دارند.
تست واحد (Unit Test) کوچکترین و سریعترین نوع تست است که روی یک بخش مستقل از کد، مثل یک تابع یا یک کلاس متمرکز میشود. این تستها بدون وابستگی به محیط، دیتابیس یا شبکه اجرا میشوند و معمولاً برای اطمینان از صحت منطق داخلی یک واحد کاربرد دارند. مزیت اصلی آنها سرعت بالا و خطایابی آسان است، اما بهتنهایی نمیتوانند تجربهی کلی کاربر را تضمین کنند.
تست یکپارچهسازی (Integration Test) برای بررسی تعامل چند بخش مختلف از سیستم در کنار یکدیگر استفاده میشود. برای مثال، میتوان ارتباط بین یک فرم React و تابع ارسال داده به API را تست کرد. این تستها کندتر از Unit Test هستند و پیکربندی بیشتری میطلبند، اما نقاط بحرانی جریان کاری را بهتر پوشش میدهند.
تست انتها به انتها (End-to-End یا E2E) کاملترین شبیهسازی مسیر کاربر در سیستم است؛ از تعامل در رابط کاربری تا تغییر داده در دیتابیس. ابزارهایی مثل Cypress ،Playwright و Puppeteer برای این کار بهکار میروند. مزیت اصلی این تستها نزدیکی بسیار زیاد به تجربهی واقعی کاربر است، اما کندی اجرا و حساسیت به تغییرات کوچک UI باعث میشود که نگهداری آنها پرهزینهتر باشد.
این سه نوع تست معمولاً در قالب هرم تست (Test Pyramid) سازماندهی میشوند. قاعدهی هرم را Unit Testها تشکیل میدهند که باید تعداد زیادی از آنها وجود داشته باشد و سریع اجرا شوند. لایهی میانی هرم، Integration Testها هستند که به تعداد متوسط نوشته میشوند. رأس هرم، تستهای E2E قرار دارند که کم ولی حیاتیاند. این ترکیب باعث میشود تستها سریع، قابلاعتماد و کمهزینه باشند.
در این ساختار، Jest برای نوشتن Unit Test و Integration Test (بهخصوص برای منطق و کدهای بدون رابط کاربری) انتخاب ایدهآلی است. در کنار آن، Testing Library ابزاری است که بیشتر برای Integration Test در سطح رابط کاربری و بررسی تجربهی کاربر بهکار میرود. ترکیب این دو ابزار امکان پوشش گسترده و کارآمد بخشهای مختلف پروژه را فراهم میکند.
نصب و راهاندازی Jest
برای شروع تستنویسی در جاوااسکریپت، اولین قدم نصب و پیکربندی Jest است. Jest یک test runner کامل است که علاوه بر اجرای تستها، امکاناتی مثل ماککردن ماژولها، مدیریت تایمرها، گزارشگیری و پشتیبانی از TypeScript را بهصورت داخلی ارائه میدهد.
نصب Jest در پروژههای جاوااسکریپت
ابتدا مطمئن شوید که Node.js و یک مدیر پکیج (npm یا Yarn یا pnpm) روی سیستم نصب است. سپس در ریشهی پروژه، دستور زیر را اجرا کنید:
npm install --save-dev jest
یا با Yarn:
yarn add --dev jest
این کار، Jest را بهعنوان یک وابستگی توسعه (devDependency) به پروژه اضافه میکند.
پیکربندی پایه Jest
برای شروع، میتوانید در فایل package.json بخش زیر را اضافه کنید:
"scripts": {
"test": "jest"
}
حالا با اجرای دستور زیر، Jest تستهای شما را پیدا و اجرا میکند:
npm test
Jest بهطور پیشفرض فایلهایی با پسوند .test.js یا .spec.js را در هر پوشهای از پروژه شناسایی میکند. برای مثال:
src/
math.js
math.test.js
اولین تست ساده
فرض کنید فایل math.js
به شکل زیر است:
function sum(a, b) {
return a + b;
}
module.exports = sum;
یک فایل تست به نام math.test.js
ایجاد کنید:
const sum = require('./math');
test('Sum', () => {
expect(sum(2, 3)).toBe(5);
});
با اجرای npm test
باید خروجی سبز رنگ و موفقیتآمیز را ببینید.
نصب Jest در پروژههای React یا TypeScript
- React: اگر با React کار میکنید، معمولاً
react-scripts
یا فریمورکهایی مثل Next.js ابزار Jest را بهصورت داخلی دارند. کافی است ساختار نامگذاری و محل فایلها را رعایت کنید. - TypeScript: باید پکیجهای
ts-jest
و@types/jest
را نصب کرده و در پیکربندی Jest، آنها را معرفی کنید.
نوشتن Unit Test پیشرفته با Jest
حالا که Jest را نصب و اولین تست ساده را نوشتیم، وقت آن است که با قابلیتهای پیشرفتهتر آن آشنا شویم. هدف این بخش، ارتقا سطح تستها از بررسیهای ساده به سناریوهایی است که در پروژههای واقعی کاربرد دارند.
استفاده از Matcherهای پیشرفته
Jest مجموعهای از matcherها (توابع مقایسه) دارد که فراتر از toBe
و toEqual
عمل میکنند:
test('آرایه شامل مقدار مورد نظر باشد', () => {
expect([1, 2, 3]).toContain(2);
});
test('رشته با الگوی مشخص سازگار باشد', () => {
expect('hello world').toMatch(/world/);
});
test('مقدار بزرگتر از یک عدد خاص باشد', () => {
expect(10).toBeGreaterThan(5);
});
این Matcherها باعث میشوند تستها خواناتر و دقیقتر باشند.
مدیریت خطاها و Exceptions
گاهی نیاز است مطمئن شوید که کد، در شرایط خاص خطا میدهد:
function divide(a, b) {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
test('تقسیم بر صفر خطا میدهد', () => {
expect(() => divide(4, 0)).toThrow('Division by zero');
});
ماککردن توابع (Mock Functions)
Mock Functionها در Jest کمک میکنند تا وابستگیها را شبیهسازی و کنترل کنید:
const fetchData = (callback) => {
setTimeout(() => callback('data'), 1000);
};
test('callback با داده درست فراخوانی شود', () => {
const mockCallback = jest.fn();
fetchData(mockCallback);
jest.runAllTimers(); // در تست async با تایمر، از این برای اجرای فوری استفاده کنید
expect(mockCallback).toHaveBeenCalledWith('data');
});
ماککردن ماژولها
Jest میتواند کل یک ماژول را ماک کند:
jest.mock('./api');
const api = require('./api');
api.getUser.mockResolvedValue({ name: 'John' });
test('نام کاربر از API دریافت شود', async () => {
const user = await api.getUser();
expect(user.name).toBe('John');
});
کنترل تایمرها و تاریخ
برای تست کدهایی که از setTimeout یا Date استفاده میکنند:
jest.useFakeTimers();
test('تایمر بهدرستی کار کند', () => {
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
});
تست تجربه کاربر با Testing Library
پس از یادگیری نوشتن Unit Testهای پیشرفته با Jest، حالا وقت آن است که به جای تمرکز بر منطق داخلی، رفتار و تجربهی کاربر را تست کنیم. این دقیقاً جایی است که Testing Library و بهطور خاص React Testing Library وارد عمل میشوند. فلسفهی اصلی این کتابخانه این است: «تست باید شبیه استفادهی واقعی کاربر باشد، نه شبیه بررسی کد منبع.»
نصب Testing Library
اگر پروژهی شما Reactی است، کافی است پکیجهای زیر را نصب کنید:
npm install --save-dev @testing-library/react @testing-library/jest-dom
@testing-library/react
ابزار اصلی برای رندر کردن و تعامل با کامپوننتها است.@testing-library/jest-dom
مجموعهای از Matcherهای جدید (مثلtoBeInTheDocument
) به Jest اضافه میکند.
در ابتدای فایلهای تست یا فایل تنظیمات Jest، این کتابخانه را فعال کنید:
import '@testing-library/jest-dom';
اولین تست با Testing Library
فرض کنید یک کامپوننت ساده داریم:
// Greeting.js
export default function Greeting({ name }) {
return <h1>سلام {name}</h1>;
}
تست آن با Testing Library:
import { render, screen } from '@testing-library/react';
import Greeting from './Greeting';
test('نام کاربر در صفحه نمایش داده شود', () => {
render(<Greeting name="علی" />);
const element = screen.getByText(/سلام علی/i);
expect(element).toBeInTheDocument();
});
اینجا screen.getByText
دقیقاً مشابه کاری است که کاربر با چشم انجام میدهد: پیدا کردن متن روی صفحه.
تست تعامل کاربر
Testing Library ابزار user-event را برای شبیهسازی تعاملات کاربر (کلیک، تایپ، فشردن کلید) فراهم میکند:
npm install --save-dev @testing-library/user-event
مثال:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
function Counter() {
const [count, setCount] = React.useState(0);
return (
<>
<span>Count: {count}</span>
<button onClick={() => setCount(count + 1)}>+</button>
</>
);
}
test('افزایش شمارنده با کلیک', async () => {
render(<Counter />);
const button = screen.getByRole('button', { name: '+' });
await userEvent.click(button);
expect(screen.getByText(/Count: 1/i)).toBeInTheDocument();
});
تست حالتهای Async
در تعاملات واقعی، بسیاری از عملیاتها زمانبر هستند (مثل دریافت داده از API). Testing Library این سناریو را با متد findBy
پوشش میدهد:
test('داده پس از بارگذاری نمایش داده شود', async () => {
render(<MyComponent />);
const item = await screen.findByText(/داده بارگذاری شد/i);
expect(item).toBeInTheDocument();
});
findBy
بهطور خودکار منتظر میماند تا عنصر مورد نظر در DOM ظاهر شود.
مزیت بزرگ این رویکرد
تستهایی که با Testing Library نوشته میشوند:
- بهجای وابستگی به ساختار داخلی، بر خروجی نهایی و تجربه کاربر متمرکز هستند.
- در برابر تغییرات جزئی پیادهسازی مقاومترند.
- باعث تشویق به نوشتن کدی میشوند که دسترسپذیرتر و قابلفهمتر است.
ترکیب Jest و Testing Library برای پوشش کامل تستها
تا اینجا یاد گرفتیم که Jest برای نوشتن Unit Test و Mock کردن وابستگیها فوقالعاده است و Testing Library برای تست رابط کاربری و تجربه کاربر. حالا وقت آن است که این دو ابزار را با هم ترکیب کنیم تا هم منطق کد و هم خروجی UI بهطور همزمان پوشش داده شوند.
سناریوی نمونه
فرض کنید یک کامپوننت React داریم که داده را از یک API دریافت کرده و نمایش میدهد. منطق دریافت داده در یک ماژول جدا پیاده شده است.
// api.js
export async function getUser() {
const res = await fetch('/api/user');
return res.json();
}
// UserInfo.js
import React from 'react';
import { getUser } from './api';
export default function UserInfo() {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
getUser().then(setUser);
}, []);
if (!user) return <div>در حال بارگذاری...</div>;
return <h1>{user.name}</h1>;
}
نوشتن تست ترکیبی
با استفاده از Jest ماژول api.js
را ماک میکنیم، سپس با Testing Library خروجی UI را بررسی میکنیم:
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import UserInfo from './UserInfo';
import * as api from './api';
jest.mock('./api');
test('نام کاربر پس از بارگذاری نمایش داده شود', async () => {
api.getUser.mockResolvedValue({ name: 'علی' });
render(<UserInfo />);
// حالت اولیه
expect(screen.getByText(/در حال بارگذاری/i)).toBeInTheDocument();
// حالت پس از دریافت داده
const nameElement = await screen.findByText('علی');
expect(nameElement).toBeInTheDocument();
});
مزیت این روش
- ایزولهسازی منطق: چون ماژول API ماک شده، تست وابسته به سرور یا شبکه واقعی نیست.
- بررسی رفتار UI: با Testing Library مطمئن میشویم که کاربر خروجی درست را میبیند.
- سرعت بالا: چون عملیاتها شبیهسازی میشوند، تست سریع اجرا میشود.
نکات حرفهای
- ترکیب Mock و Async: همیشه در تستهای Async از متدهایی مثل
findBy
یاwaitFor
استفاده کنید تا از مشکلات تایمینگ جلوگیری شود. - ساخت Data Builder: برای دادههای تکراری از الگوهایی مثل
Test Data Builder
استفاده کنید تا کد تست تمیز بماند. - پوششدهی مناسب: با دستور
npm test -- --coverage
مطمئن شوید که بخشهای کلیدی کدتان تحت پوشش تستها هستند.
اندازهگیری و بهبود پوشش تست (Test Coverage)
نوشتن تستهای خوب، تنها بخشی از ماجراست. برای اطمینان از اینکه بخشهای کلیدی پروژه تحت پوشش قرار گرفتهاند، باید پوشش تستها را اندازهگیری و در صورت نیاز بهبود داد. Jest این قابلیت را بهصورت داخلی فراهم میکند.
فعالسازی پوشش تست در Jest
برای گرفتن گزارش پوشش، کافی است دستور زیر را اجرا کنید:
npm test -- --coverage
یا با Yarn:
yarn test --coverage
پس از اجرا، Jest گزارشی شامل درصد پوشش خطوط (Lines)، دستورات (Statements)، توابع (Functions) و شاخهها (Branches) ارائه میدهد.
خواندن گزارش پوشش
نمونه خروجی:
File | % Stmts | % Branch | % Funcs | % Lines |
------------|---------|----------|---------|---------|
All files | 85.71 | 75.00 | 80.00 | 85.71 |
math.js | 100 | 100 | 100 | 100 |
utils.js | 70.00 | 50.00 | 66.67 | 70.00 |
% Stmts (Statements)
: درصد تمام دستورات اجرا شده.% Branch (Branches)
: درصد شاخههای منطقی (if, switch, ternary) که تست شدهاند.% Funcs (Functions)
: درصد توابعی که تست شدهاند.% Lines (Lines)
: درصد خطوط کد تحت پوشش تست.
هدفگذاری پوشش تست
در فایل پیکربندی Jest (jest.config.js یا package.json) میتوانید حداقل پوشش مورد نظر را تعیین کنید:
// jest.config.js
module.exports = {
collectCoverage: true,
coverageThreshold: {
global: {
statements: 80,
branches: 70,
functions: 80,
lines: 80,
},
},
};
اگر پوشش کمتر از حد تعیینشده باشد، تستها شکست میخورند.
بهبود پوشش تست
- شناسایی بخشهای بدون پوشش: گزارش Jest دقیقاً خطوط بدون تست را مشخص میکند.
- تمرکز بر کد حیاتی: ابتدا مسیرهای اصلی اجرای برنامه (critical paths) را پوشش دهید.
- نوشتن تست برای شاخههای منطقی: برای تمام حالتهای if و switch تست بنویسید.
- حذف کد مرده: اگر کدی هیچگاه اجرا نمیشود، شاید باید حذف شود.
در پایان
تستنویسی در جاوااسکریپت با ترکیب Jest و Testing Library میتواند به شکلی کارآمد، هم منطق کد و هم تجربه کاربر را پوشش دهد. در این راهنما دیدیم که چگونه از مفاهیم پایه مثل Unit Test و Integration Test شروع کنیم، Jest را نصب و پیکربندی کنیم، تستهای پیشرفته بنویسیم، با Testing Library تعاملات واقعی کاربر را شبیهسازی کنیم، و در نهایت با استفاده از قابلیت Test Coverage مطمئن شویم بخشهای کلیدی پروژه تحت پوشش تست قرار گرفتهاند.
مزیت اصلی این رویکرد، ایجاد تستهایی است که سریع، قابلاعتماد و مقاوم در برابر تغییرات هستند و در عین حال از نظر معنا، به تجربهی واقعی کاربر نزدیکاند. با رعایت اصول گفتهشده، تستها نهتنها مانع پیشرفت نخواهند بود، بلکه به ابزاری ارزشمند برای توسعه پایدار و ایمن نرمافزار تبدیل میشوند.
با گذشت زمان و گسترش پروژه، ترکیب این دو ابزار به شما کمک میکند با اطمینان بیشتری تغییرات را اعمال کنید، خطاها را زودتر شناسایی کنید، و کدی بنویسید که نهتنها امروز، بلکه در آینده نیز قابلاعتماد باقی بماند.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید