Как тестировать Angular-компоненты с Jasmine/Karma?angular-94

Настройка тестовой среды

Angular CLI автоматически настраивает Karma и Jasmine при создании проекта. Конфигурация находится в:

  1. karma.conf.js - настройки Karma (браузеры, reporters)
  2. test.ts - точка входа для тестов
  3. angular.json - раздел test с конфигурацией

Базовая структура теста компонента

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent } from './my.component';

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

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [MyComponent]
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Основные техники тестирования

1. Тестирование @Input и @Output

it('should accept input', () => {
  component.inputValue = 'test';
  fixture.detectChanges();
  expect(fixture.nativeElement.textContent).toContain('test');
});

it('should emit output', () => {
  spyOn(component.outputEvent, 'emit');
  component.triggerOutput();
  expect(component.outputEvent.emit).toHaveBeenCalledWith('expected value');
});

2. Тестирование шаблона

it('should render title', () => {
  component.title = 'Test Title';
  fixture.detectChanges();
  const el = fixture.nativeElement.querySelector('h1');
  expect(el.textContent).toContain('Test Title');
});

it('should have disabled button when isLoading is true', () => {
  component.isLoading = true;
  fixture.detectChanges();
  const button = fixture.nativeElement.querySelector('button');
  expect(button.disabled).toBeTrue();
});

3. Тестирование сервисов и зависимостей

let mockService = jasmine.createSpyObj('DataService', ['fetchData']);

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [
      { provide: DataService, useValue: mockService }
    ]
  });
});

it('should call service method', () => {
  mockService.fetchData.and.returnValue(of([]));
  component.loadData();
  expect(mockService.fetchData).toHaveBeenCalled();
});

4. Асинхронное тестирование

it('should show data after async operation', fakeAsync(() => {
  let service = TestBed.inject(DataService);
  spyOn(service, 'getData').and.returnValue(
    of({ data: 'test' }).pipe(delay(1000))
  );

  component.loadData();
  tick(1000);
  fixture.detectChanges();

  expect(fixture.nativeElement.textContent).toContain('test');
}));

Продвинутые техники

1. Тестирование роутинга

import { RouterTestingModule } from '@angular/router/testing';

beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [RouterTestingModule]
  });
});

it('should navigate on click', () => {
  const router = TestBed.inject(Router);
  spyOn(router, 'navigate');

  const link = fixture.nativeElement.querySelector('a');
  link.click();

  expect(router.navigate).toHaveBeenCalledWith(['/expected-route']);
});

2. Тестирование форм

it('should update form control value', () => {
  const input = fixture.nativeElement.querySelector('input');
  input.value = 'new value';
  input.dispatchEvent(new Event('input'));
  fixture.detectChanges();

  expect(component.form.value.name).toBe('new value');
});

it('should validate required field', () => {
  const control = component.form.get('name');
  control.setValue('');
  expect(control.valid).toBeFalse();
});

3. Тестирование с HostComponent

@Component({ template: `<app-test [input]="value"></app-test>` })
class HostComponent {
  value = 'initial';
}

describe('TestComponent with host', () => {
  let hostFixture: ComponentFixture<HostComponent>;

  beforeEach(() => {
    hostFixture = TestBed.createComponent(HostComponent);
    hostFixture.detectChanges();
  });

  it('should react to input changes', () => {
    hostFixture.componentInstance.value = 'changed';
    hostFixture.detectChanges();
    expect(hostFixture.nativeElement.textContent).toContain('changed');
  });
});

Советы по эффективному тестированию

  1. Используйте Page Object Pattern:
class Page {
  get buttons() {
    return this.fixture.nativeElement.querySelectorAll('button');
  }

  constructor(private fixture: ComponentFixture<TestComponent>) {}
}
  1. Проверяйте не только успешные сценарии:
it('should show error when service fails', () => {
  mockService.fetchData.and.returnValue(throwError('error'));
  component.loadData();
  fixture.detectChanges();
  expect(fixture.nativeElement.querySelector('.error')).not.toBeNull();
});
  1. Избегайте избыточных тестов - тестируйте поведение, а не реализацию

  2. Используйте Angular Testing Utilities:

  • async/fakeAsync для асинхронных операций
  • ComponentFixtureAutoDetect для автоматического обнаружения изменений
  • By.css для поиска элементов

Резюмируем

Тестирование Angular-компонентов с Jasmine/Karma требует понимания как работы Angular, так и принципов модульного тестирования. Начинайте с простых тестов на создание компонента, постепенно переходя к более сложным сценариям с моками зависимостей, асинхронными операциями и проверкой шаблонов. Правильно написанные тесты становятся надежной защитой от регрессий и документацией к вашему коду.