Виконання юніт-тестів є важливою практикою в розробці додатків на Angular. Вони гарантують, що компоненти та сервіси працюють як очікується, допомагаючи уникати помилок протягом усього життєвого циклу програмного забезпечення.
У цій статті ми розглянемо концепцію AAA (“Arrange, Act, Assert”), застосовану до юніт-тестів, і побудуємо практичний приклад тесту для компонента, що використовує сервіс.
Давайте почнемо з кількох визначень:
Юніт-тест — це, в основному, спосіб тестувати маленькі частини коду, такі як функції або методи, ізольовано. Ідея полягає в тому, щоб переконатися, що кожен фрагмент коду виконується відповідно до очікувань, не турбуючись про решту системи. Коли ми проводимо юніт-тести, ми зосереджуємося на тестуванні лише однієї одиниці коду за раз, зазвичай за допомогою інструментів для імітації зовнішніх частин, від яких залежить цей код, щоб тест був чітко сфокусований. В Angular зазвичай використовують Jasmine для написання тестів і Karma для їх виконання в браузері.
Що таке AAA в юніт-тестах?
- Arrange (Підготовка): Налаштування тестового середовища, включаючи ініціалізацію залежностей та налаштування початкових значень.
- Act (Дія): Виконання функціональності, що тестується.
- Assert (Перевірка): Перевірка очікуваного результату.
Цей підхід робить тести більш організованими та зрозумілими.
Практичний сценарій
Створимо юніт-тест для компонента Angular, який залежить від сервісу, що звертається до API та повертає список об'єктів.
Структура компонента та сервісу
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class DataService {
getItems(): Observable<{ id: number; name: string }[]> {
return of([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]);
}
}
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-item-list',
template: '
{{ item.name }}
' })
export class ItemListComponent implements OnInit {
data: { id: number; name: string }[] = [];
constructor(private dataService: DataService) {}
ngOnInit(): void {
this.dataService.getItems().subscribe((response) => {
this.data = response;
});
}
}
Написання юніт-тесту:
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { ItemListComponent } from './item-list.component';
import { DataService } from './data.service';
import { of } from 'rxjs';
describe('ItemListComponent', () => {
let component: ItemListComponent;
let fixture: ComponentFixture<ItemListComponent>;
let dataService: jasmine.SpyObj<DataService>;
beforeEach(() => {
const spy = jasmine.createSpyObj('DataService', ['getItems']);
TestBed.configureTestingModule({
declarations: [ItemListComponent],
providers: [
{ provide: DataService, useValue: spy }
]
});
fixture = TestBed.createComponent(ItemListComponent);
component = fixture.componentInstance;
dataService = TestBed.inject(DataService) as jasmine.SpyObj<DataService>;
});
it('повинен відображати елементи, що повертаються сервісом', () => {
// Arrange: Визначення повернутого значення мокованого методу
const mockData = [
{ id: 1, name: 'Mock Item 1' },
{ id: 2, name: 'Mock Item 2' }
];
dataService.getItems.and.returnValue(of(mockData));
// Act: Виклик методу ngOnInit
component.ngOnInit();
// Assert: Перевірка, чи дані правильні
expect(component.data).toEqual(mockData);
});
});
Пояснення коду:
Метод beforeEach
— це функція, яка виконується перед кожним тестом в блоці describe
.
Він дуже корисний, коли потрібно налаштувати середовище або ініціалізувати змінні, які будуть використовуватися в кількох тестах, що дозволяє уникати повторення коду.
beforeEach(() => {
// Створюємо мок для DataService за допомогою jasmine.createSpyObj
// jasmine.createSpyObj створює змодельований об'єкт з вказаними методами.
// Тут ми створюємо мок для DataService з методом getItems.
const spy = jasmine.createSpyObj('DataService', ['getItems']);
// Налаштовуємо TestBed, середовище тестування для Angular.
// TestBed.configureTestingModule налаштовує тестовий модуль,
// включаючи компоненти та сервіси.
TestBed.configureTestingModule({
declarations: [ItemListComponent], // Оголошуємо компонент для тестування.
providers: [
{ provide: DataService, useValue: spy } // Замінюємо реальний сервіс на мок.
]
});
// Створюємо екземпляр компонента для прямого маніпулювання
// під час тесту.
fixture = TestBed.createComponent(ItemListComponent);
component = fixture.componentInstance;
// Отримуємо мок для DataService, щоб налаштувати його в тесті.
dataService = TestBed.inject(DataService) as jasmine.SpyObj<DataService>;
});
Метод it
використовується для визначення тесту. Він описує, що саме перевіряє тест, роблячи це дуже конкретно. Всередині it
ви вказуєте логіку вашого тесту, де зазвичай використовуються асерції (як-от expect
), щоб перевірити, чи працює код згідно з очікуваннями.
it('повинен відображати елементи, що повертаються сервісом', () => {
// Arrange: Налаштовуємо повернення методу getItems у моку.
// Тут ми імітуємо відповідь від API, яка повертає Observable з мокованими даними.
const mockData = [
{ id: 1, name: 'Mock Item 1' },
{ id: 2, name: 'Mock Item 2' }
];
dataService.getItems.and.returnValue(of(mockData));
// Act: Викликаємо метод ngOnInit компонента, який використовує сервіс.
component.ngOnInit();
// Assert: Перевіряємо, чи містить властивість data моковані елементи, що повертаються сервісом.
expect(component.data).toEqual(mockData);
});
Висновок
Висновок
Застосування концепції AAA в юніт-тестах полегшує підтримку та розуміння коду. У наведеному прикладі ми імітуємо звичайний сценарій використання API і навчаємося тестувати компоненти Angular, які залежать від сервісів. Крім того, ми пояснили, як використовувати інструменти, такі як TestBed
та jasmine.createSpyObj
, для створення більш ефективних тестів.
Важливо підкреслити, що мокання сервісів, від яких залежить компонент, дозволяє уникнути проблем під час тестування, гарантуючи, що перевіряється лише поведінка, ізольована від зовнішніх реалізацій. Хоча тест може працювати і з реальною інстанцією сервісу, це компрометує характеристики юніт-тесту, наближаючи його до інтеграційного тесту, що може призвести до небажаних залежностей і менш передбачуваних результатів.
Це лише початок — по мірі вашого розвитку ви можете досліджувати більш складні тести для різних сценаріїв і функціональностей. Якщо у вас є питання або ви хочете побачити більше прикладів, залишайте коментарі нижче! Якщо у вас є питання або ви хочете побачити більше прикладів, залишайте коментарі нижче!
Перекладено з: Compreendendo teste unitário em Angular — Iniciante