>
← Повернутися на головну

Автоматичне Unit Testing

Практичний гайд з Jest, Vitest, TDD та Code Coverage

📅 Оновлено: 7 лютого 2026 ⏱️ Час читання: 40 хв 🎯 Рівень: Початківець — Середній
📘 Що таке Unit Testing?

Unit tests — автоматизоване тестування окремих "юнітів" коду (функцій, класів, модулів) у ізоляції від решти системи:

  • ✅ Швидкі (виконуються за мілісекунди)
  • ✅ Ізольовані (не залежать від БД, API, файлової системи)
  • ✅ Повторювані (завжди один результат для одних параметрів)
  • ✅ Автоматизовані (запускаються CI/CD при кожному commit)

Частина 1: Порівняння Test Frameworks

Характеристика Jest Vitest Mocha + Chai
Швидкість ⭐⭐⭐ Помірно ⭐⭐⭐⭐⭐ Дуже швидко (Vite) ⭐⭐⭐⭐ Швидко
Налаштування ⭐⭐⭐⭐⭐ Zero-config ⭐⭐⭐⭐⭐ Zero-config (якщо Vite) ⭐⭐ Потребує конфігурації
Assertions ✅ Вбудовані ✅ Вбудовані (сумісні з Jest) ❌ Потрібен Chai
Mocking ✅ Вбудоване ✅ Вбудоване ❌ Потрібен Sinon
Code Coverage ✅ Вбудоване (Istanbul) ✅ Вбудоване (c8) ❌ Потрібен NYC
Паралельність ✅ Так (workers) ✅ Так (threads) ❌ Ні (або з Mocha-parallel)
Snapshot Testing ✅ Вбудоване ✅ Вбудоване ❌ Потрібен окремий пакет
TypeScript ✅ (з ts-jest) ✅ Native підтримка ✅ (з ts-node)

Частина 2: Jest — швидкий старт

1 Встановлення та налаштування
# Встановлення npm install --save-dev jest # Ініціалізація конфігурації npx jest --init # Запуск тестів npm test # Запуск з coverage npm test -- --coverage # Watch mode (автоматичний перезапуск) npm test -- --watch

Додайте у package.json:

{ "scripts": { "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", "test:verbose": "jest --verbose" }, "jest": { "testEnvironment": "node", "coverageDirectory": "coverage", "collectCoverageFrom": [ "src/**/*.{js,jsx}", "!src/**/*.test.{js,jsx}", "!src/index.js" ], "coverageThreshold": { "global": { "branches": 80, "functions": 80, "lines": 80, "statements": 80 } } } }
2 Перший тест: Математичні функції

Тестований код (src/math.js):

// Квадратне рівняння: ax² + bx + c = 0 export function solveQuadratic(a, b, c) { if (a === 0) { throw new Error('Коефіцієнт a не може бути 0'); } const discriminant = b * b - 4 * a * c; if (discriminant < 0) { return { message: 'Дискримінант менше нуля, коренів немає' }; } if (discriminant === 0) { const x = -b / (2 * a); return { x1: x, x2: x }; } const sqrtD = Math.sqrt(discriminant); const x1 = (-b + sqrtD) / (2 * a); const x2 = (-b - sqrtD) / (2 * a); return { x1, x2 }; } export function factorial(n) { if (n < 0) throw new Error('Факторіал від негативного числа не існує'); if (n === 0 || n === 1) return 1; return n * factorial(n - 1); }

Тести (src/math.test.js):

import { solveQuadratic, factorial } from './math.js'; describe('solveQuadratic', () => { test('розв'язує рівняння x² - 5x + 6 = 0', () => { const result = solveQuadratic(1, -5, 6); expect(result).toEqual({ x1: 3, x2: 2 }); }); test('повертає один корінь для D=0', () => { const result = solveQuadratic(1, -4, 4); expect(result).toEqual({ x1: 2, x2: 2 }); }); test('повертає повідомлення для D<0', () => { const result = solveQuadratic(1, 2, 5); expect(result).toHaveProperty('message'); expect(result.message).toContain('Дискримінант менше нуля'); }); test('викидає помилку при a=0', () => { expect(() => solveQuadratic(0, 2, 3)).toThrow('Коефіцієнт a не може бути 0'); }); }); describe('factorial', () => { test('обчислює 5! = 120', () => { expect(factorial(5)).toBe(120); }); test('0! = 1', () => { expect(factorial(0)).toBe(1); }); test('1! = 1', () => { expect(factorial(1)).toBe(1); }); test('викидає помилку для негативних чисел', () => { expect(() => factorial(-5)).toThrow('Факторіал від негативного числа не існує'); }); });

Jest Matchers Cheat Sheet

// ✅ Equality expect(value).toBe(5) // Strict equality (===) expect(value).toEqual({ a: 1 }) // Deep equality expect(value).not.toBe(null) // Negation // 🔢 Numbers expect(value).toBeGreaterThan(3) expect(value).toBeGreaterThanOrEqual(3.5) expect(value).toBeLessThan(5) expect(value).toBeCloseTo(0.3) // Для float чисел // 📝 Strings expect(value).toMatch(/pattern/) expect(value).toContain('substring') expect(value).toHaveLength(10) // 📦 Arrays/Objects expect(array).toContain(item) expect(array).toHaveLength(3) expect(obj).toHaveProperty('key', 'value') expect(obj).toMatchObject({ subset }) // ✅ Truthiness expect(value).toBeTruthy() expect(value).toBeFalsy() expect(value).toBeNull() expect(value).toBeUndefined() expect(value).toBeDefined() // 🚨 Exceptions expect(() => fn()).toThrow() expect(() => fn()).toThrow(Error) expect(() => fn()).toThrow('error message') // ⏱️ Async await expect(promise).resolves.toBe(value) await expect(promise).rejects.toThrow() // 📸 Snapshots expect(component).toMatchSnapshot()

Частина 3: Vitest — сучасна альтернатива

1 Встановлення Vitest
# Встановлення npm install --save-dev vitest # Запуск npx vitest # С UI інтерфейсом npx vitest --ui # З coverage npx vitest --coverage

Конфігурація vitest.config.ts:

import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { globals: true, // describe, test, expect без import environment: 'jsdom', // Для DOM testing coverage: { provider: 'c8', reporter: ['text', 'json', 'html'], exclude: [ 'node_modules/', 'tests/', ], }, setupFiles: './tests/setup.ts', }, });

Тести (math.test.ts) — синтаксис ідентичний Jest:

import { describe, it, expect } from 'vitest'; import { solveQuadratic } from './math'; describe('solveQuadratic', () => { it('solves x² - 5x + 6 = 0', () => { const result = solveQuadratic(1, -5, 6); expect(result).toEqual({ x1: 3, x2: 2 }); }); });

Частина 4: Test-Driven Development (TDD)

📘 TDD Цикл ("Red-Green-Refactor"):
  1. 🔴 Red: Напишіть тест (він падає, бо функції ще немає)
  2. 🟢 Green: Напишіть мінімальний код, щоб тест пройшов
  3. 🔵 Refactor: Покращте код, не ламаючи тести
1 TDD Приклад: Функція isPrime()

Крок 1 (🔴 Red): Пишемо тест спочатку:

// isPrime.test.js import { isPrime } from './isPrime'; describe('isPrime', () => { test('2 is prime', () => { expect(isPrime(2)).toBe(true); }); test('1 is not prime', () => { expect(isPrime(1)).toBe(false); }); test('15 is not prime', () => { expect(isPrime(15)).toBe(false); }); test('17 is prime', () => { expect(isPrime(17)).toBe(true); }); });

Крок 2 (🟢 Green): Пишемо код, щоб тести пройшли:

// isPrime.js export function isPrime(n) { if (n <= 1) return false; if (n === 2) return true; if (n % 2 === 0) return false; for (let i = 3; i <= Math.sqrt(n); i += 2) { if (n % i === 0) return false; } return true; }

Крок 3 (🔵 Refactor): Оптимізуємо без зламування тестів.

Частина 5: Mocking

Mock Functions

// Mock функція const mockFn = jest.fn(); mockFn('arg1', 'arg2'); // Перевірка викликів expect(mockFn).toHaveBeenCalled(); expect(mockFn).toHaveBeenCalledTimes(1); expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2'); // Mock реалізація const mockAdd = jest.fn((a, b) => a + b); expect(mockAdd(2, 3)).toBe(5); // Mock return values mockFn.mockReturnValue(42); mockFn.mockReturnValueOnce(1).mockReturnValueOnce(2); // Mock resolved/rejected promises mockFn.mockResolvedValue({ data: 'success' }); mockFn.mockRejectedValue(new Error('API error'));

Mock Modules

// api.js export async function fetchUser(id) { const response = await fetch(`https://api.example.com/users/${id}`); return response.json(); } // user.service.test.js import { fetchUser } from './api'; jest.mock('./api'); // Mock всього модуля describe('User Service', () => { test('gets user data', async () => { // Mock implementation fetchUser.mockResolvedValue({ id: 1, name: 'John' }); const user = await fetchUser(1); expect(user).toEqual({ id: 1, name: 'John' }); expect(fetchUser).toHaveBeenCalledWith(1); }); });

Частина 6: Code Coverage

1 Налаштування Coverage
# Запуск з coverage npm test -- --coverage # Тільки змінені файли npm test -- --coverage --onlyChanged # З threshold npm test -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'

Coverage звіт показує:

  • Statements — відсоток покритих інструкцій
  • Branches — відсоток покритих гілок (if/else)
  • Functions — відсоток покритих функцій
  • Lines — відсоток покритих рядків
💡 Рекомендований Coverage:
  • 🎯 80%+ — для критичної бізнес-логіки
  • 🎯 60-80% — для звичайних проектів
  • 100% — not realistic, не варто прагнути

Частина 7: CI Integration

GitHub Actions

name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Run tests run: npm test -- --coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage/coverage-final.json - name: Fail if coverage < 80% run: | COVERAGE=$(jq '.total.lines.pct' coverage/coverage-summary.json) if (( $(echo "$COVERAGE < 80" | bc -l) )); then echo "Coverage $COVERAGE% is below 80%" exit 1 fi

Частина 8: Best Practices

✅ Рекомендації:
  1. AAA Pattern: Arrange → Act → Assert
    test('adds numbers', () => { // Arrange const a = 2, b = 3; // Act const result = add(a, b); // Assert expect(result).toBe(5); });
  2. Один assert на тест (коли можливо)
  3. Описові назви тестів: test('should throw error when dividing by zero')
  4. Тестуйте поведінку, а не реалізацію
  5. Ізолюйте тести — не залежати один від одного
  6. Mock зовнішні залежності (API, БД)
  7. Швидкі тести — unit tests мають виконуватись за секунди
⚠️ Чого уникати:
  • ❌ Не тестуйте приватні методи безпосередньо
  • ❌ Не тестуйте третійсторонні бібліотеки
  • ❌ Не ігноруйте failed tests
  • ❌ Не робіть тести залежними від порядку виконання

Частина 9: Advanced Mocking Strategies

Mocking Modules

// src/api.js export async function fetchCalculations() { const response = await fetch('/api/calculations'); return response.json(); } // src/calculator.js import { fetchCalculations } from './api'; export async function loadPreviousResults() { const data = await fetchCalculations(); return data.results; } // tests/calculator.test.js import { loadPreviousResults } from '../src/calculator'; import * as api from '../src/api'; // Mock весь модуль jest.mock('../src/api'); test('loads previous results', async () => { // Arrange const mockData = { results: [1, 2, 3] }; api.fetchCalculations.mockResolvedValue(mockData); // Act const results = await loadPreviousResults(); // Assert expect(results).toEqual([1, 2, 3]); expect(api.fetchCalculations).toHaveBeenCalledTimes(1); });

Mocking Timers

// src/debounce.js export function debounce(fn, delay) { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => fn(...args), delay); }; } // tests/debounce.test.js import { debounce } from '../src/debounce'; test('debounce calls function after delay', () => { jest.useFakeTimers(); // ✨ Mock timers const callback = jest.fn(); const debouncedFn = debounce(callback, 1000); debouncedFn('test1'); debouncedFn('test2'); debouncedFn('test3'); // Після 500ms ще не викликано jest.advanceTimersByTime(500); expect(callback).not.toHaveBeenCalled(); // Після 1000ms викликано один раз з останнім аргументом jest.advanceTimersByTime(500); expect(callback).toHaveBeenCalledTimes(1); expect(callback).toHaveBeenCalledWith('test3'); jest.useRealTimers(); // ✨ Restore timers });

Mocking Classes

// src/Database.js export class Database { constructor(connectionString) { this.connection = connectionString; } async query(sql) { // Real database query } } // tests/Database.test.js import { Database } from '../src/Database'; jest.mock('../src/Database'); test('mocked database', async () => { const mockQuery = jest.fn().mockResolvedValue([{ id: 1, value: 42 }]); Database.mockImplementation(() => ({ query: mockQuery })); const db = new Database('mock://connection'); const results = await db.query('SELECT * FROM calculations'); expect(results).toEqual([{ id: 1, value: 42 }]); expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM calculations'); });

Частина 10: Snapshot Testing

Component Snapshot Testing

// src/components/CalculatorButton.jsx export function CalculatorButton({ value, onClick }) { return ( ); } // tests/CalculatorButton.test.jsx import { render } from '@testing-library/react'; import { CalculatorButton } from '../src/components/CalculatorButton'; test('CalculatorButton snapshot', () => { const { container } = render( ); // Перший запуск створює snapshot файл expect(container).toMatchSnapshot(); });

Після першого запуску створюється файл __snapshots__/CalculatorButton.test.jsx.snap:

// Jest Snapshot v1 exports[`CalculatorButton snapshot 1`] = `
`;
✅ Коли використовувати snapshots:
  • UI компоненти (кнопки, карточки, навбари)
  • JSON responses (API validation)
  • Generated HTML/SVG
  • Error messages
⚠️ Коли НЕ використовувати:
  • ❌ Динамічні дані (timestamp, random IDs)
  • ❌ Великі об'єкти (> 100 lines)
  • ❌ Часто змінювані компоненти
# Оновити всі snapshots npm test -- -u

Inline Snapshots

test('format number', () => { expect(formatNumber(1234567.89)).toMatchInlineSnapshot(`"1,234,567.89"`); // Snapshot автоматично вставляється в код ↑ });

Частина 11: Testing Async Code

Promises

// src/api.js export function calculate(operation, a, b) { return fetch('/api/calculate', { method: 'POST', body: JSON.stringify({ operation, a, b }) }).then(res => res.json()); } // tests/api.test.js test('calculate returns result', () => { return calculate('add', 5, 3).then(data => { expect(data.result).toBe(8); }); }); // Або з async/await test('calculate returns result', async () => { const data = await calculate('add', 5, 3); expect(data.result).toBe(8); });

Callbacks

// src/calculator.js export function calculateAsync(a, b, callback) { setTimeout(() => { callback(a + b); }, 100); } // tests/calculator.test.js test('calculateAsync calls callback', (done) => { calculateAsync(5, 3, (result) => { expect(result).toBe(8); done(); // ✨ Повідомляє Jest що тест завершено }); });

Resolves/Rejects

// src/validator.js export function validateInput(value) { if (value < 0) { return Promise.reject(new Error('Value must be positive')); } return Promise.resolve(value); } // tests/validator.test.js test('validates positive number', () => { return expect(validateInput(5)).resolves.toBe(5); }); test('rejects negative number', () => { return expect(validateInput(-1)).rejects.toThrow('Value must be positive'); }); // Або з async/await test('rejects negative number', async () => { await expect(validateInput(-1)).rejects.toThrow('Value must be positive'); });

Частина 12: Mutation Testing

Що таке Mutation Testing?

Mutation Testing — це техніка перевірки якості тестів шляхом внесення мутацій (змін) у код і перевірки, чи тести виявляють ці зміни.
  • ✅ Виявляє слабкі тести
  • ✅ Покращує Code Coverage якість (не просто кількість)
  • ✅ Знаходить недотестований код

Stryker Mutator для JavaScript

1 Налаштування Stryker
  1. Встановіть Stryker:
    npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
  2. Ініціалізація:
    npx stryker init
  3. Створюється stryker.config.json:
    { "testRunner": "jest", "coverageAnalysis": "perTest", "mutate": ["src/**/*.js", "!src/**/*.test.js"] }
  4. Запуск:
    npx stryker run

Приклад мутацій

// Оригінальний код function add(a, b) { return a + b; // ← Це буде змінено } // Stryker створює мутації: // 1. Arithmetic Operator → return a - b; // 2. Arithmetic Operator → return a * b; // 3. Remove Return → /* removed return */ // Якщо тести НЕ падають для мутації → mutant survived ⚠️ // Якщо тести падають → mutant killed ✅
Mutation Score Оцінка Ваша дія
90-100% Відмінно ✅
80-89% Добре ✅ Покращити слабкі зони
60-79% Прийнятно ⚠️ Додати тести
< 60% Погано ❌ Переписати тести

Частина 13: Test Debugging

VS Code Debugger для Jest

// .vscode/launch.json { "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Jest Debug", "program": "${workspaceFolder}/node_modules/.bin/je", "args": [ "--runInBand", "--no-cache", "${file}" ], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen" } ] }
✅ Debugging workflow:
  1. Поставте breakpoint у тесті (клік на номері рядка)
  2. Відкрийте потрібний test file
  3. F5 → запустити debugger
  4. Використовуйте Step Over (F10), Step Into (F11)
  5. Переглядайте змінні у Debug panel

Debug Failing Tests

// Використовуйте .only для запуску одного тесту test.only('debug this test', () => { const result = complexFunction(); console.log('Result:', result); // Логування expect(result).toBe(42); }); // Пропустити тест test.skip('skip this test', () => { // Не буде виконано });

Watch Mode у Jest

# Watch mode — автоматично перезапускає тести при змінах npm test -- --watch # Опції в watch mode: # › Press a to run all tests. # › Press f to run only failed tests. # › Press p to filter by a filename regex pattern. # › Press t to filter by a test name regex pattern. # › Press q to quit watch mode.

Частина 14: Integration Tests vs Unit Tests

Різниця між Unit і Integration тестами

Характеристика Unit тести Integration тести
Що тестують Окрема функція/клас Взаємодію між модулями
Залежності Моки/стаби Реальні (або частково)
Швидкість ⚡ Дуже швидко (ms) ⚠️ Повільніше (секунди)
Складність Проста Складніша
Кількість Багато (70%) Менше (20%)
Приклад add(2, 3) === 5 API → DB → Response

Test Pyramid

/\ / \ E2E Tests (10%) / \ Slower, expensive /------\ / \ Integration Tests (20%) / \ Medium speed /------------\ / \ Unit Tests (70%) /________________\ Fast, many tests

Integration Test приклад

// src/calculatorService.js import { Database } from './Database'; import { calculate } from './calculator'; export class CalculatorService { constructor() { this.db = new Database('sqlite://test.db'); } async saveCalculation(operation, a, b) { const result = calculate(operation, a, b); await this.db.query( 'INSERT INTO calculations (operation, a, b, result) VALUES (?, ?, ?, ?)', [operation, a, b, result] ); return result; } } // tests/calculatorService.integration.test.js import { CalculatorService } from '../src/calculatorService'; import { Database } from '../src/Database'; describe('CalculatorService Integration', () => { let service; let db; beforeEach(async () => { // Справжня БД (або test database) db = new Database('sqlite://test.db'); await db.query('CREATE TABLE IF NOT EXISTS calculations (...)'); service = new CalculatorService(); }); afterEach(async () => { await db.query('DROP TABLE calculations'); }); test('saves calculation to database', async () => { const result = await service.saveCalculation('add', 5, 3); expect(result).toBe(8); // Перевірити що дані в БД const rows = await db.query('SELECT * FROM calculations'); expect(rows).toHaveLength(1); expect(rows[0].result).toBe(8); }); });

Висновок

Автоматичне unit тестування — фундамент якісного коду:

🚀 Рекомендований workflow:
  1. Встановіть Jest або Vitest
  2. Налаштуйте coverage threshold (80%)
  3. Додайте pre-commit hook для запуску тестів
  4. Інтегруйте у CI/CD
  5. Моніторьте coverage через Codecov

Як користуватися шпаргалкою

Ця шпаргалка зосереджує найважливіші формули, правила та визначення теми в компактному форматі для швидкого пошуку та підготовки до іспитів. Матеріал систематизований від базових понять до просунутих результатів.

Ефективне використання

Використовуйте шпаргалку поряд з розв'язуванням задач — не для списування, а як довідник формул. Спершу спробуйте пригадати формулу самостійно, потім звіртеся з довідником. Регулярне повторення формує стійку пам'ять.

Часті запитання (FAQ)

Які ключові формули та правила містить шпаргалка з автоматичне unit testing?
Ця шпаргалка з 'Автоматичне Unit Testing' включає: основні означення, головні формули у компактному вигляді, правила обчислень, типові підстановки та приклади застосування. Все систематизовано для швидкого пошуку.
Для кого призначена ця шпаргалка з автоматичне unit testing?
Шпаргалка з 'Автоматичне Unit Testing' орієнтована на студентів університетів та учнів старшої школи, а також на всіх, хто хоче швидко освіжити знання перед іспитом або при вирішенні практичних задач.
Як використовувати шпаргалку з автоматичне unit testing при підготовці до іспиту?
Оптимальна стратегія: спершу вивчіть теорію, потім використовуйте шпаргалку як довідник при розв'язанні задач. За 1–2 дні до іспиту перегляньте шпаргалку цілком, звертаючи увагу на формули, які ви плутаєте.
Чи охоплює ця шпаргалка всю програму курсу з автоматичне unit testing?
Шпаргалка з 'Автоматичне Unit Testing' охоплює стандартну університетську програму: всі ключові теореми, формули та методи. Матеріал структурований від базових понять до просунутих результатів.
Де ще можна попрактикуватися з автоматичне unit testing після вивчення шпаргалки?
Після роботи зі шпаргалкою рекомендуємо: тренажери вправ на calculator.party (миттєвий зворотний зв'язок), розв'язані задачі (показують метод покроково) та онлайн-калькулятори для перевірки власних результатів.