Angular предоставляет специальные утилиты для работы с асинхронным кодом:
import { fakeAsync, tick, async } from '@angular/core/testing';
it('should update value after timeout', fakeAsync(() => {
let value = 0;
setTimeout(() => {
value = 1;
}, 1000);
expect(value).toBe(0); // До таймера
tick(1000); // Перемещаем время вперед
expect(value).toBe(1); // После таймера
}));
it('should handle delayed observable', fakeAsync(() => {
const service = TestBed.inject(DataService);
let result: any;
service.getDelayedData().subscribe(data => {
result = data;
});
tick(500); // Проходит 500ms
expect(result).toBeUndefined();
tick(500); // Еще 500ms (всего 1000ms)
expect(result).toEqual({ id: 1 });
}));
it('should show data after async operation', async(() => {
const fixture = TestBed.createComponent(AsyncComponent);
const component = fixture.componentInstance;
component.loadData(); // Запускает асинхронную операцию
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges(); // Обновляем шаблон после завершения асинхронных операций
const element = fixture.nativeElement.querySelector('.data');
expect(element.textContent).toContain('Loaded data');
});
}));
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
describe('DataService', () => {
let httpMock: HttpTestingController;
let service: DataService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [DataService]
});
httpMock = TestBed.inject(HttpTestingController);
service = TestBed.inject(DataService);
});
it('should return expected data', fakeAsync(() => {
const testData = { id: 1, name: 'Test' };
let response: any;
service.getData().subscribe(data => {
response = data;
});
const req = httpMock.expectOne('api/data');
req.flush(testData);
tick();
expect(response).toEqual(testData);
}));
});
it('should show content when data is loaded', fakeAsync(() => {
const fixture = TestBed.createComponent(AsyncPipeComponent);
const component = fixture.componentInstance;
component.data$ = of({ id: 1 }).pipe(delay(100));
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('.content')).toBeNull();
tick(100);
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('.content')).not.toBeNull();
}));
it('should validate username uniqueness', fakeAsync(() => {
const control = new FormControl('taken_username');
const validator = uniqueUsernameValidator(TestBed.inject(UserService));
validator(control).subscribe(validationResult => {
expect(validationResult).toEqual({ usernameTaken: true });
});
const req = httpMock.expectOne('/api/check-username');
req.flush({ exists: true });
tick();
}));
it('should handle 404 error', fakeAsync(() => {
let errorResponse: any;
service.getData().subscribe(
() => fail('should have failed'),
error => errorResponse = error
);
const req = httpMock.expectOne('api/data');
req.flush('Not found', { status: 404, statusText: 'Not found' });
tick();
expect(errorResponse.status).toBe(404);
}));
it('should handle multiple async operations', fakeAsync(() => {
let value = '';
of('first').pipe(
delay(100),
switchMap(() => of('second').pipe(delay(200)))
).subscribe(result => {
value = result;
});
tick(100);
expect(value).toBe('');
tick(200);
expect(value).toBe('second');
}));
it('should process microtasks', fakeAsync(() => {
let value = 0;
Promise.resolve().then(() => value = 1);
expect(value).toBe(0);
flush(); // Обрабатывает все микротаски
expect(value).toBe(1);
}));
fakeAsync
для синхронного тестирования асинхронного кодаfakeAsync
для таймеров, async
для сложных сценариевtick()
и flush()
для управления временемhttpMock.verify()
для HTTP Angular предоставляет мощные инструменты (async
, fakeAsync
, tick
, flush
) для тестирования асинхронного кода. Для HTTP-запросов используйте HttpClientTestingModule
, для таймеров - fakeAsync
с tick
, для сложных сценариев - async
с whenStable
. Правильное тестирование асинхронного кода критически важно для создания надежных Angular-приложений.