describe,itvəexpectilə modul testləri yazmaq və uyğun müqayisə metodunu (toBe,toEqual,toThrow) seçmək- Asinxron kodu və xətaları test etmək, kodu mock, spy və saxta taymerlərlə kənar dünyadan ayırmaq
- Yaxşı təcrübələrə əməl etmək: Arrange–Act–Assert, test piramidası, örtük və TDD
Endirim funksiyasındakı səhvi düzəldirsən — və ödəniş səhifəsi başqa yerdə sınır. Hər dəyişiklikdən sonra saytı əl ilə yoxlamaq saatlar aparır. Avtomatik testlər isə yüzlərlə halı bir saniyədə, hər dəfə faylı saxlayanda yoxlayır. JavaScript üçün ən populyar test alətləri Vitest və Jest-dir; onların API-ləri demək olar ki, eynidir, ona görə bu dərsdə öyrəndiklərin hər ikisində işləyəcək.
İlk modul testi
Modul testi (unit test) kiçik bir hissəni — adətən bir funksiyanı — təcrid olunmuş halda yoxlayır. Test faylı kodun yanında durur: price.js üçün price.test.js. describe testləri qruplaşdırır, it (və ya test) bir davranışı təsvir edir, expect(qiymət).toBe(gözlənilən) isə yoxlama aparır. npx vitest testləri işə salır və faylları izləyir.
// price.js
export function applyDiscount(price, percent) {
if (percent < 0 || percent > 100) throw new RangeError('Percent must be 0-100');
return Math.round(price * (100 - percent)) / 100;
}
// price.test.js
import { describe, it, expect } from 'vitest';
import { applyDiscount } from './price.js';
describe('applyDiscount', () => {
it('reduces the price by the given percent', () => {
expect(applyDiscount(200, 10)).toBe(180);
});
it('rounds to cents', () => {
expect(applyDiscount(9.99, 15)).toBe(8.49);
});
it('rejects a percent above 100', () => {
expect(() => applyDiscount(50, 120)).toThrow(RangeError);
});
}); ✓ price.test.js (3 tests) 3ms
Test Files 1 passed (1)
Tests 3 passed (3)toThrow üçün çağırışı ox funksiyasına sarmaq lazımdır — yoxsa xəta expect-dən əvvəl atılar.expect necə işləyir
Test aləti sehrli deyil: it funksiyanı try...catch içində çağırır, expect isə şərt ödənməyəndə xəta atır. Aşağıdakı mini versiya bunu göstərir və iki vacib fərqi üzə çıxarır: toBe Object.is ilə müqayisə edir (eyni istinad), toEqual isə qiymətləri müqayisə edir; kəsr ədədləri isə birbaşa bərabərliklə yoxlamaq təhlükəlidir.
const results = [];
function it(name, fn) {
try {
fn();
results.push(`✓ ${name}`);
} catch (error) {
results.push(`✗ ${name}: ${error.message}`);
}
}
function expect(actual) {
const show = (v) => JSON.stringify(v);
return {
toBe(expected) {
if (!Object.is(actual, expected)) throw new Error(`expected ${show(expected)}, got ${show(actual)}`);
},
toEqual(expected) {
if (show(actual) !== show(expected)) throw new Error(`expected ${show(expected)}, got ${show(actual)}`);
},
};
}
it('adds numbers', () => expect(2 + 3).toBe(5));
it('compares arrays by value', () => expect([1, 2]).toEqual([1, 2]));
it('toBe needs the same array', () => expect([1, 2]).toBe([1, 2]));
it('decimals are tricky', () => expect(0.1 + 0.2).toBe(0.3));
console.log(results.join('\n'));▸ Gözlənilən nəticə
✓ adds numbers ✓ compares arrays by value ✗ toBe needs the same array: expected [1,2], got [1,2] ✗ decimals are tricky: expected 0.3, got 0.30000000000000004
toBeCloseTo(0.3) var. Bizim toEqual sadələşdirilib — Vitest və Jest dərin müqayisəni daha diqqətlə aparır.| Müqayisə metodu | Nəyi yoxlayır |
|---|---|
toBe(x) | Object.is ilə bərabərlik: primitivlər, eyni istinad |
toEqual(x) | obyekt və massivlərin dərin bərabərliyi |
toBeCloseTo(x) | kəsr ədədlərin təxmini bərabərliyi |
toContain(x), toHaveLength(n) | massivdə element və ya sətirdə hissə; uzunluq |
toThrow(Type) | funksiyanın xəta atması |
resolves / rejects | Promise-in nəticəsi və ya xətası |
.not | istənilən yoxlamanı inkar edir: expect(x).not.toBe(0) |
Asinxron kod, mock və spy-lar
Asinxron test sadəcə async funksiyadır: await ilə nəticəni gözlə və ya await expect(promise).rejects.toThrow() yaz. Modul testi real e-poçt göndərməməli, real serverə müraciət etməməli və real vaxtı gözləməməlidir. Bunun üçün mock (saxta funksiya, vi.fn()), spy (mövcud metodun çağırışlarını izləyən, vi.spyOn) və saxta taymerlər (vi.useFakeTimers()) var. Ən sadə yol — asılılığı parametr kimi ötürmək: onda testdə real funksiyanın yerinə mock verirsən.
// notify.js
export async function notifyUser(user, sendEmail) {
return sendEmail(user.email, `Welcome, ${user.name}!`);
}
// notify.test.js
import { describe, it, expect, vi } from 'vitest';
import { notifyUser } from './notify.js';
describe('notifyUser', () => {
it('sends one welcome email', async () => {
const sendEmail = vi.fn().mockResolvedValue({ ok: true });
await notifyUser({ email: 'leyla@mail.az', name: 'Leyla' }, sendEmail);
expect(sendEmail).toHaveBeenCalledTimes(1);
expect(sendEmail).toHaveBeenCalledWith('leyla@mail.az', 'Welcome, Leyla!');
});
it('fails when the mail service is down', async () => {
const sendEmail = vi.fn().mockRejectedValue(new Error('SMTP down'));
await expect(notifyUser({ email: 'a@mail.az', name: 'Elvin' }, sendEmail)).rejects.toThrow('SMTP down');
});
}); ✓ notify.test.js (2 tests) 4ms
Test Files 1 passed (1)
Tests 2 passed (2)vi əvəzinə jest ilə yazılır: jest.fn(), jest.spyOn(), jest.useFakeTimers().Yaxşı testlərin qaydaları
- Arrange–Act–Assert: hazırla, icra et, yoxla — hər test bu üç hissədən ibarətdir.
- Bir test — bir davranış, adı isə nəyin gözlənildiyini deyir: «rejects a percent above 100».
- Daxili quruluşu yox, davranışı test et — onda kodu yenidən yazanda testlər sınmır.
- Test piramidası: çoxlu sürətli modul testləri, daha az inteqrasiya testləri, bir neçə uçdan-uca (end-to-end) test.
Testlər qırmızıdır: isLeapYear-də səhv var. Testlərə toxunmadan funksiyanı düzəlt ki, dörd test yaşıl olsun. Qayda: il 4-ə bölünürsə, uzun ildir, amma 100-ə bölünən illər yalnız 400-ə də bölünəndə uzun ildir.
function isLeapYear(year) {
return year % 4 === 0;
}
const results = [];
function it(name, fn) {
try {
fn();
results.push(`✓ ${name}`);
} catch (error) {
results.push(`✗ ${name}: ${error.message}`);
}
}
const expect = (actual) => ({
toBe(expected) {
if (actual !== expected) throw new Error(`expected ${expected}, got ${actual}`);
},
});
it('2024 is a leap year', () => expect(isLeapYear(2024)).toBe(true));
it('2023 is not a leap year', () => expect(isLeapYear(2023)).toBe(false));
it('1900 is not a leap year', () => expect(isLeapYear(1900)).toBe(false));
it('2000 is a leap year', () => expect(isLeapYear(2000)).toBe(true));
console.log(results.join('\n'));▸ Gözlənilən nəticə
✓ 2024 is a leap year ✓ 2023 is not a leap year ✓ 1900 is not a leap year ✓ 2000 is a leap year
Öz spy-ını yaz: createSpy() çağırılanda arqumentlərini spy.calls massivinə yazan funksiya qaytarsın. Sonra testin göstərdiyi kimi, notifyAll yalnız abunəçilərə məktub göndərməlidir — iki çağırış və ikinci çağırışın arqumentləri çap olunur.
function createSpy() {
const spy = (...args) => {
// record args in spy.calls
};
spy.calls = [];
return spy;
}
function notifyAll(users, send) {
for (const user of users) {
if (user.subscribed) send(user.email, 'New lesson is out!');
}
}
const send = createSpy();
notifyAll([
{ email: 'aysel@mail.az', subscribed: true },
{ email: 'murad@mail.az', subscribed: false },
{ email: 'leyla@mail.az', subscribed: true },
], send);
console.log('calls:', send.calls.length);
console.log(send.calls[1]);▸ Gözlənilən nəticə
calls: 2 [ 'leyla@mail.az', 'New lesson is out!' ]
Əsas fikirlər
- Modul testi bir kiçik davranışı yoxlayır:
describeqruplaşdırır,ittəsvir edir,expect(...).matcher()yoxlayır. toBeObject.isilə,toEqualqiymətlərlə müqayisə edir; kəsrlər üçüntoBeCloseTo, xətalar üçüntoThrowişlət.- Asinxron testlərdə
awaitvə yaresolves/rejects; mock, spy və saxta taymerlər kodu şəbəkədən, vaxtdan və təsadüfdən ayırır. - Arrange–Act–Assert, bir testdə bir davranış, müstəqil testlər; çoxlu modul, az inteqrasiya və uçdan-uca testlər.
- Örtük yalnız hansı sətirlərin işlədiyini göstərir; TDD: qırmızı → yaşıl → təmizlə.
Özünü yoxla
10 sual. Hər düzgün cavab XP qazandırır.
expect([1, 2]).toBe([1, 2]) nə ilə nəticələnir?