Формы в Angular — один из самых насыщенных логикой элементов приложения. Они объединяют шаблоны, модели данных, валидацию, асинхронные операции и пользовательские события. Ошибка в форме почти всегда напрямую влияет на пользовательский опыт и корректность данных. Поэтому тестирование форм рассматривается как обязательная часть разработки, а не дополнительная мера.
Angular предоставляет встроенные инструменты для работы с формами и их тестирования, опираясь на Jasmine, Karma и TestBed. Подходы различаются в зависимости от типа формы: template-driven forms и reactive forms.
Тестирование форм выполняется в стандартной тестовой среде Angular:
TestBed — конфигурация тестового модуляComponentFixture — доступ к экземпляру компонента и
DOMDebugElement — работа с элементами шаблонаFormsModule и ReactiveFormsModule —
обязательны для тестирования формПример базовой конфигурации:
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [MyFormComponent],
imports: [ReactiveFormsModule]
}).compileComponents();
});
Отсутствие нужного модуля приводит к ошибкам вида
No value accessor for form control.
Reactive Forms строятся на основе FormGroup,
FormControl и FormArray, что делает их
особенно удобными для юнит-тестирования.
Первый уровень тестирования — корректное создание структуры формы:
it('должна создаваться форма с нужными контролами', () => {
expect(component.form.contains('email')).toBeTrue();
expect(component.form.contains('password')).toBeTrue();
});
Важно проверять:
it('должен принимать email', () => {
const control = component.form.get('email');
control?.setValue('test@example.com');
expect(control?.value).toBe('test@example.com');
});
Проверка значений выполняется без взаимодействия с DOM, что ускоряет тесты и снижает их хрупкость.
it('email должен быть невалидным при пустом значении', () => {
const control = component.form.get('email');
control?.setValue('');
expect(control?.valid).toBeFalse();
});
Для кастомных валидаторов проверяется:
errorsexpect(control?.errors?.['required']).toBeTrue();
Angular формы имеют состояния:
valid / invalidtouched / untoucheddirty / pristineit('форма должна быть dirty после изменения', () => {
component.form.get('email')?.setValue('a@a.ru');
expect(component.form.dirty).toBeTrue();
});
Template-driven формы зависят от шаблона и директив
ngModel, поэтому тестирование чаще выполняется через
DOM.
imports: [FormsModule]
Без этого директивы формы не будут работать.
@ViewChild('form') form!: NgForm;
Тестирование выполняется после
fixture.detectChanges().
it('форма должна быть невалидной при пустых полях', () => {
expect(component.form.valid).toBeFalse();
});
const input: HTMLInputElement = fixture.nativeElement.querySelector('input[name="email"]');
input.value = 'test@test.ru';
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
После этого Angular обновляет модель формы.
Форма считается протестированной не полностью, если не проверено отображение ошибок пользователю.
it('должно отображаться сообщение об ошибке', () => {
const control = component.form.get('email');
control?.setValue('');
control?.markAsTouched();
fixture.detectChanges();
const error = fixture.nativeElement.querySelector('.error');
expect(error).toBeTruthy();
});
Ключевые моменты:
toucheddetectChangesОтправка формы почти всегда связана с сервисами.
it('должен вызываться метод submit', () => {
spyOn(component, 'onSubmit');
component.form.setValue({
email: 'test@test.ru',
password: '123456'
});
fixture.nativeElement.querySelector('form').dispatchEvent(new Event('submit'));
expect(component.onSubmit).toHaveBeenCalled();
});
Формы часто отправляют данные на сервер. Используется
spyOn или подмена сервиса:
const authService = TestBed.inject(AuthService);
spyOn(authService, 'login').and.returnValue(of(true));
Проверка вызова:
expect(authService.login).toHaveBeenCalledWith({
email: 'test@test.ru',
password: '123456'
});
Асинхронные валидаторы требуют fakeAsync и
tick.
it('должен быть невалиден при занятом email', fakeAsync(() => {
const control = component.form.get('email');
control?.setValue('taken@mail.ru');
tick(500);
expect(control?.valid).toBeFalse();
}));
it('должен добавляться элемент в массив', () => {
const array = component.form.get('phones') as FormArray;
component.addPhone();
expect(array.length).toBe(1);
});
Также проверяется:
Изоляция логики
fixture.detectChanges()Читаемость тестов
Надёжность
form.get() вместо
querySelectorПроизводительность
shallow testing, если шаблон сложныйdetectChangestouched и
dirtyТестирование форм в Angular — это сочетание юнит-тестов и DOM-проверок, где основная логика должна оставаться максимально изолированной и предсказуемой.