Тестирование Angular компонентов

Cypress — это инструмент для end-to-end (E2E) тестирования, который также отлично подходит для интеграционного тестирования Angular приложений. Он позволяет запускать тесты в реальном браузере, взаимодействовать с компонентами и проверять их поведение на уровне пользовательского интерфейса.


Настройка Cypress для Angular проекта

Для интеграции Cypress в Angular проект используется стандартная установка через npm:

npm install cypress --save-dev

После установки Cypress создаёт структуру папок cypress/, включающую fixtures, integration, support и plugins. В Angular проектах важно правильно настроить baseUrl в cypress.config.js для корректной работы с локальным сервером:

import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:4200',
    supportFile: 'cypress/support/e2e.js'
  }
})

Структура тестов

Тесты в Cypress пишутся с использованием синтаксиса Mocha (describe, it) и Chai для ассертов. Для Angular компонентов важно использовать селекторы, которые устойчивы к изменениям структуры DOM. Наиболее надёжным подходом является использование data-testid атрибутов:

<button data-testid="submit-btn">Отправить</button>

В тесте:

describe('Компонент кнопки', () => {
  it('должен корректно реагировать на клик', () => {
    cy.visit('/');
    cy.get('[data-testid="submit-btn"]').click();
    cy.get('.notification').should('contain', 'Данные отправлены');
  });
});

Тестирование компонентов с формами

Angular активно использует реактивные формы и FormControl. Cypress позволяет эмулировать пользовательский ввод и проверять реакцию компонентов:

describe('Форма авторизации', () => {
  it('валидирует поля ввода', () => {
    cy.visit('/login');

    cy.get('[data-testid="username"]').type('testuser');
    cy.get('[data-testid="password"]').type('wrongpassword');
    cy.get('[data-testid="login-btn"]').click();

    cy.get('.error-message').should('contain', 'Неверный пароль');
  });
});

Ключевой момент — Cypress ждёт элементы автоматически до заданного таймаута, что упрощает работу с асинхронными обновлениями Angular.


Тестирование компонентов с HTTP-запросами

Для тестирования взаимодействия с сервером в Angular используется HttpClient. Cypress позволяет перехватывать и мокать HTTP-запросы через cy.intercept():

describe('Загрузка данных пользователей', () => {
  it('отображает список пользователей', () => {
    cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
    
    cy.visit('/users');
    cy.wait('@getUsers');
    
    cy.get('.user-item').should('have.length', 3);
    cy.get('.user-item').first().should('contain', 'Иван Иванов');
  });
});

Использование фикстур позволяет создавать стабильные тестовые сценарии без зависимости от реального API.


Проверка состояния компонентов

Angular часто использует внутреннее состояние для отображения данных. Cypress позволяет проверять наличие классов, атрибутов и текста для подтверждения корректного состояния компонента:

describe('Компонент переключателя', () => {
  it('должен менять состояние при клике', () => {
    cy.visit('/toggle');
    
    cy.get('[data-testid="toggle-btn"]').click();
    cy.get('[data-testid="toggle-btn"]').should('have.class', 'active');
    
    cy.get('[data-testid="toggle-btn"]').click();
    cy.get('[data-testid="toggle-btn"]').should('not.have.class', 'active');
  });
});

Параметризация тестов

Cypress поддерживает динамическое создание тестов с использованием forEach или cy.each(), что полезно для Angular компонентов с множеством состояний:

const roles = ['admin', 'user', 'guest'];

roles.forEach(role => {
  it(`проверяет доступ для роли ${role}`, () => {
    cy.visit(`/dashboard?role=${role}`);
    cy.get('.dashboard').should('contain', `Роль: ${role}`);
  });
});

Взаимодействие с Angular Material и сторонними библиотеками

Компоненты Angular Material часто используют тени DOM и динамические шаблоны (mat-select, mat-dialog). Cypress может взаимодействовать с ними через стандартные селекторы и события:

describe('Диалог Material', () => {
  it('открывает и закрывает диалог', () => {
    cy.visit('/dialogs');
    cy.get('[data-testid="open-dialog"]').click();
    cy.get('mat-dialog-container').should('exist');
    cy.get('mat-dialog-container button.close').click();
    cy.get('mat-dialog-container').should('not.exist');
  });
});

Важно учитывать задержки анимаций и применять { force: true } для клика по элементам, если они временно перекрыты.


Использование команд Cypress для Angular

Для уменьшения дублирования кода можно создавать собственные команды в cypress/support/commands.js:

Cypress.Commands.add('login', (username, password) => {
  cy.visit('/login');
  cy.get('[data-testid="username"]').type(username);
  cy.get('[data-testid="password"]').type(password);
  cy.get('[data-testid="login-btn"]').click();
});

После этого команды можно использовать в тестах многократно:

describe('Проверка авторизации', () => {
  it('успешный вход', () => {
    cy.login('admin', 'admin123');
    cy.get('.dashboard').should('contain', 'Добро пожаловать, admin');
  });
});

Асинхронные проверки и таймауты

Angular приложения активно используют асинхронные операции. Cypress по умолчанию ждёт до 4 секунд (defaultCommandTimeout) на каждый элемент. Для сложных компонентов можно задавать индивидуальные таймауты:

cy.get('.loading-indicator', { timeout: 10000 }).should('not.exist');

Интеграция с CI/CD

Тесты Cypress легко интегрируются с системами CI/CD, такими как GitHub Actions, GitLab CI или Jenkins. Для Angular проектов обычно используют команду:

npx cypress run --headless

Это позволяет запускать E2E тесты на каждом коммите, обеспечивая стабильность компонентов и предотвращая регрессии.


Тщательная структура тестов, использование data-testid, перехват HTTP-запросов и создание пользовательских команд позволяет построить надёжную стратегию тестирования Angular компонентов с помощью Cypress.