Модуль 35

Тестування Angular: Unit тести з Jasmine та Karma

Ось готовий урок, створений спеціально за твоїм запитом у стилі CS50.


🎓 Тема: Тестування Angular: Unit-тести з Jasmine та Karma

Привіт, світе! 👋 Я радий бачити вас на цьому занятті.

Сьогодні ми не просто пишемо код. Сьогодні ми вчимося писати код, який захищає інший код. Ми поговоримо про Unit Testing (юніт-тестування) в Angular, використовуючи інструменти Jasmine та Karma.


1. 🔥 Вступ: Чому ми взагалі це робимо?

Уявіть ситуацію. Ви будуєте величезний хмарочос 🏢. Ви кладете цеглу за цеглою, поверх за поверхом. І ось, на 50-му поверсі, ви вирішуєте замінити вікно на першому поверсі. Ви міняєте скло... і раптом весь хмарочос розсипається, як картковий будиночок.

Звучить як нічний жах архітектора, правда? Але в програмуванні це трапляється щодня. Це називається регресія — коли нова зміна ламає щось старе, що чудово працювало.

Запитання до вас: Як часто ви фіксили один баг, а створювали два нових? (Я підіймаю руку — я робив це сотні разів).

Якби ми будували літак, ми б не запускали його в рейс з пасажирами, щоб перевірити, чи працюють двигуни, вірно? Ми б перевіряли кожну заклепку, кожен гвинтик окремо в ангарі.

Ось що таке Unit-тести: 1. Це ваш "ангар", де ви перевіряєте кожну деталь (компонент, сервіс) ізольовано. 2. Це ваша "страховка", яка дозволяє спати спокійно, знаючи, що ваші зміни не "завалили" продакшн.

Без тестів ви — сапер, який працює навпомацки. З тестами ви — інженер з планшетом, у якого все під контролем.


2. 🧠 Теоретична база: Хто такі Jasmine та Karma?

В Angular тестування часто лякає новачків страшними словами. Давайте спростимо. У нас є два головних герої:

🎭 1. Jasmine (Джасмін) — Це Сценарист

Jasmine — це бібліотека (framework) для написання тестів. Вона дає нам мову, синтаксис. Уявіть, що ви пишете сценарій для п'єси: * "Ось сцена (describe)..." * "У цій сцені актор має зробити сальто (it)..." * "Я очікую, що він приземлиться на ноги (expect)..."

Jasmine нічого не запускає. Вона просто описує, як має поводитися код.

🏃‍♂️ 2. Karma (Карма) — Це Режисер (Runner)

Karma — це інструмент, який бере ваш сценарій (Jasmine), відкриває браузер (Chrome, Firefox), запускає там ваш код і каже: "Так, сценарій виконано успішно" або "Ні, актор впав обличчям у підлогу".

Що "під капотом"? (Інтуїтивне розуміння)

Коли ви запускаєте ng test: 1. Angular компілює ваш код. 2. Karma відкриває вікно браузера. 3. Код виконується в цьому браузері. 4. Результати (зелені галочки або червоні хрестики) повертаються вам у консоль.

🔑 Словничок (запам'ятайте ці три слова):

  1. describe(): Група тестів (наприклад, "Тести для Компонента Логіну").
  2. it(): Конкретний тест-кейс (наприклад, "Має показувати помилку, якщо пароль короткий").
  3. expect(): Перевірка (наприклад, "Очікую, що errorMessage дорівнює 'Too short'").

3. 🧪 Приклади: Від простого до реального

Приклад 1: Чиста логіка (Без Angular)

Уявіть, у нас є функція, яка додає два числа. Що ви очікуєте, якщо передати туди 2 і 2? Правильно, 4. Давайте напишемо це мовою Jasmine.

// calculator.ts (Уявна функція)
function add(a, b) {
  return a + b;
}

// calculator.spec.ts (Наш тест)
describe('Калькулятор', () => { // 1. Описуємо групу

  it('має повертати 4, якщо додати 2 і 2', () => { // 2. Описуємо дію
    const result = add(2, 2);
    expect(result).toBe(4); // 3. Перевіряємо результат
  });

});

Це було просто, еге ж? toBe — це як суворе "дорівнює".


Приклад 2: Angular Компонент

А тепер реальний світ. У нас є компонент UserComponent, який має властивість username.

// user.component.ts
export class UserComponent {
  username = 'Гість';
}

Як нам протестувати це? Angular дає нам суперсилу — TestBed. Аналогія: TestBed — це як лабораторія або "пісочниця". Ми створюємо компонент не в реальному додатку, а в стерильних умовах, де можемо його "мацати" з усіх боків.

// user.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserComponent } from './user.component';

describe('UserComponent', () => {
  let component: UserComponent;
  let fixture: ComponentFixture<UserComponent>;

  beforeEach(async () => {
    // Налаштовуємо "лабораторію"
    await TestBed.configureTestingModule({
      declarations: [ UserComponent ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    // Створюємо компонент перед кожним тестом
    fixture = TestBed.createComponent(UserComponent);
    component = fixture.componentInstance; 
    fixture.detectChanges(); // Запускаємо зміни (ngOnInit і т.д.)
  });

  // 👇 ВАШ ПЕРШИЙ СПРАВЖНІЙ ТЕСТ
  it('має створити компонент', () => {
    expect(component).toBeTruthy(); // Чи існує він взагалі?
  });

  it(`має мати ім'я 'Гість' за замовчуванням`, () => {
    expect(component.username).toEqual('Гість');
  });
});

Чому так? beforeEach гарантує, що перед кожним тестом ми маємо чистий, свіжий компонент. Ми не хочемо, щоб тест №1 забруднив дані для тесту №2.


Приклад 3: Взаємодія з HTML (DOM)

Давайте ускладнимо. При натисканні кнопки заголовок має змінитися. Ви очікуєте: Клік -> Текст змінюється.

<!-- user.component.html -->
<h1>{{ title }}</h1>
<button (click)="changeTitle()">Натисни мене</button>
// user.component.ts
export class UserComponent {
  title = 'Привіт';
  changeTitle() {
    this.title = 'Па-па!';
  }
}

Тест:

it('має змінити заголовок після кліку', () => {
  // 1. Перевіряємо початковий стан
  expect(component.title).toBe('Привіт');

  // 2. Симулюємо дію (виклик методу)
  component.changeTitle();

  // 3. Змушуємо Angular оновити HTML
  fixture.detectChanges();

  // 4. Отримуємо елемент з HTML
  const compiled = fixture.nativeElement as HTMLElement;
  const h1 = compiled.querySelector('h1');

  // 5. Перевіряємо, що бачить користувач
  expect(h1?.textContent).toContain('Па-па!');
});

Важливий нюанс: Angular не оновлює HTML миттєво в тестах. Ми маємо вручну сказати fixture.detectChanges(). Це як сказати браузеру: "Ей, перемалюй картинку!".


4. 🛠 Практична частина

Прийшов час забруднити руки! Відкрийте свій IDE або уявіть, що ви на співбесіді.

Завдання 1: Математика У вас є метод calculateDiscount(price: number, percent: number). Напишіть тест (блок it), який перевіряє, що calculateDiscount(100, 20) повертає 80.

Завдання 2: Логіка перемикача У компоненті є булева змінна isOn = false і метод toggle(), який змінює її на протилежну. Напишіть тест: 1. Викликати toggle(). 2. Перевірити, що isOn стало true. 3. Викликати toggle() ще раз. 4. Перевірити, що isOn знову false.

Завдання 3: Знайди баг (Дебаггінг) Тест падає з помилкою: Expected 'User' to equal 'Admin'. Код компонента: role = 'User'. Код тесту: expect(component.role).toEqual('Admin');. Питання: Що треба виправити — код чи тест? (Підказка: залежить від бізнес-вимог, але зазвичай ми підганяємо код під вимоги).

Завдання 4: Міні-кейс Створіть тест для кнопки "Увійти". * Початковий текст кнопки: "Login". * Після виклику методу login(), змінна isLoading стає true. * Перевірте, що змінна змінилася.

Завдання 5: А що, якщо...? Що буде, якщо в Завданні 1 передати від'ємний відсоток знижки? Подумайте: Як досвідчений розробник, ви маєте написати тест і на цей випадок (наприклад, функція має кинути помилку або повернути початкову ціну).


5. 💡 Мислення як у розробника

Ось кілька секретів, які відрізняють "джуна" від "сеньйора" у тестуванні:

  1. Не тестуйте фреймворк.

    • Помилка: Писати тест, який перевіряє, чи працює *ngIf. Angular команда в Google вже це протестувала.
    • Правильно: Тестуйте, чи ваша логіка правильно перемикає умову для *ngIf.
  2. Red-Green-Refactor.

    • Це мантра TDD (Test Driven Development).
    • Спочатку напишіть тест, який падає (Red).
    • Потім напишіть код, щоб тест пройшов (Green).
    • Потім покращте код (Refactor), не ламаючи тест.
  3. Тести — це документація.

    • Коли ви приходите в новий проєкт, не читайте код. Читайте describe та it у тестах. Вони розкажуть вам, що має робити система, людською мовою.

6. 🧩 Підсумок

Ну що, друзі, як відчуття? Ви тільки що зробили крок від "я сподіваюся, воно працює" до "я знаю, що воно працює".

Що ви тепер вмієте: * ✅ Розумієте різницю між Jasmine (сценарій) та Karma (сцена). * ✅ Вмієте написати базовий тест для класу та методу. * ✅ Знаєте, як використовувати TestBed для перевірки Angular-компонентів. * ✅ Вмієте перевіряти, чи змінився текст у HTML.

Наступного разу: А що робити, коли компонент робить запит на сервер за даними? Ми ж не хочемо реально стукати на сервер під час тестів? На наступному уроці ми вивчимо Mocks & Spies (Моки та Шпигуни) — як обдурити компонент і підсунути йому фейкові дані. Це буде справжня шпигунська гра! 🕵️‍♂️

А поки що — пишіть тести, ламайте їх і робіть зеленими знову. Це і є розробка! Побачимось! 👋


Якщо ви готові продовжувати:

Скажіть "Згенеруй завдання для моків", і ми підемо далі!