Основы тестирования React-приложений через Cypress

10896a19bc064cfe82abee23a9ebe5c6.png

Привет, Хабр! Сегодня рассмотрим как автоматизировать тестирование React-приложений с инструментом Cypress.

Для начала работы с Cypress React-проекте, первым делом нужно установить сам пакет. Это можно сделать с помощью npm или Yarn:

npm install cypress --save-dev
# или
yarn add cypress --dev

После установки, нужно будет проинициализировать Cypress, что создаст базовую структуру папок и файлов конфигураций:

npx cypress open

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

Структура будет выглядеть следующим образом:

  • cypress/fixtures/: папка для хранения фикстур — файлов с данными, которые могут быть использованы в тестах для имитации ответов сервера, загрузки данных и т. п

  • cypress/integration/: здесь располагаются тесты. Cypress автоматически поиск тестов в этой директории.

  • cypress/plugins/: папка для настройки плагинов, которые могут изменять поведение Cypress на более низком уровне, о них поговорим позже.

  • cypress/support/: содержит файлы, которые выполняются перед загрузкой тестов. Это идеальное место для размещения повторно используемых функций или настроек, доступных глобально во всех тестах.

  • cypress.json: файл конфигурации Cypress, где можно задать различные настройки, такие как базовые URL-адреса, время ожидания для тестов, переменные окружения и др.

  • package.json: стандартный файл Node.js, содержащий информацию о проекте и его зависимостях. В этом файле также можно настроить скрипты для запуска тестов.

Пример базовой конфигурации cypress.json:

{
  "baseUrl": "http://example.com",
  "defaultCommandTimeout": 10000,
  "pageLoadTimeout": 30000,
  "viewportWidth": 1280,
  "viewportHeight": 720,
  "testFiles": "**/*.spec.js"
}

Здесь:

  • baseUrl задает базовый URL, к которому будут добавляться относительные URL в ваших тестах.

  • defaultCommandTimeout задает таймаут по умолчанию для команд Cypress в миллисекундах (в данном случае 10 секунд).

  • pageLoadTimeout задает максимальное время ожидания загрузки страницы (30 секунд).

  • viewportWidth и viewportHeight задают ширину и высоту окна браузера для выполнения тестов.

  • estFiles указывает шаблон для файлов с тестами (в данном случае, все файлы с расширением .spec.js в любых подпапках).

Основной синтаксис и структура тестов в Cypress

Cypress использует цепочку команд. Тесты обычно организованы с помощью функций describe и it, которые являются частью глобального API Mocha.

Пример базового теста:

describe('Login Test', function() {
  it('Visits the login page', function() {
    cy.visit('https://example.com/login') // переход на страницу логина
    cy.get('input[name=username]').type('user1') // вводим имя пользователя
    cy.get('input[name=password]').type('password123') // вводим пароль
    cy.get('form').submit() // отправка формы
    cy.url().should('include', '/dashboard') // проверка URL после логина
  })
})

Поиск элементов осуществляется с помощью команды cy.get(), которая принимает строку селектора. Cypress поддерживает большинство селекторов CSS.

Взаимодействие с элементами возможно через команды вроде .click(), .type(), .check() для разных типов элементов.

Ассерты проверяют ожидаемое состояние или поведение элементов. Cypress интегрируется с Chai, предоставляя множество утверждений через .should() или .expect().

Примеры:

cy.get('.list-item').should('have.length', 5) // проверка количества элементов
cy.get('.alert').should('not.exist') // элемент не должен существовать

Cypress автоматически управляет асинхронностью, поэтому очень редко придется явно использовать async/await. Например, если вы делаете запрос к API, Cypress дождется его завершения, прежде чем двигаться дальше:

cy.request('POST', '/api/users', {name: 'Alex'}).then((response) => {
  expect(response.body).to.have.property('name', 'Alex') // проверка ответа
})

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

cy.intercept('GET', '/api/users', {fixture: 'users.json'}).as('getUsers')
cy.wait('@getUsers').its('response.statusCode').should('eq', 200)

Можно использовать псевдонимы для сохранения данных и их последующего использования в тестах:

cy.get('input[name=firstname]').type('Jane').as('firstname')
cy.get('@firstname').should('have.value', 'Jane')

Cypress может делать снимки состояния приложения:

cy.visit('/settings')
cy.get('.theme').click()
cy.screenshot() // снимок изменённой темы

Примеры использования

Тестирование счётчика

Для компонента счётчика, который увеличивает или уменьшает значение при нажатии кнопок, тесты в Cypress могут проверять начальное состояние и реакцию на пользовательские действия:

describe('Counter Component', () => {
  beforeEach(() => {
    cy.visit('/');
  });

  it('should render the counter with initial value', () => {
    cy.get('h1').contains('Counter: 0');
  });

  it('should increment the counter when increment button is clicked', () => {
    cy.get('button').contains('Increment').click();
    cy.get('h1').contains('Counter: 1');
  });

  it('should decrement the counter when decrement button is clicked', () => {
    cy.get('button').contains('Increment').click();
    cy.get('button').contains('Decrement').click();
    cy.get('h1').contains('Counter: 0');
  });
});

Тест проверяет правильность начального отображения компонента и его изменение при взаимодействии пользователя с элементами управления.

Тестирование формы входа

Тестирование формы входа включает в себя проверку ввода данных пользователя и реакции формы на отправку:

describe('Login Form', () => {
  beforeEach(() => {
    cy.visit('/login');
  });

  it('should allow a user to type in the username and password', () => {
    cy.get('input[name="username"]').type('testuser');
    cy.get('input[name="password"]').type('qwerty');
  });

  it('should show an error message for invalid credentials', () => {
    cy.get('input[name="username"]').type('invaliduser');
    cy.get('input[name="password"]').type('wrongpassword');
    cy.get('button[type="submit"]').click();
    cy.get('.error-message').should('be.visible').and('contain', 'Invalid credentials');
  });

  it('should redirect to dashboard on successful login', () => {
    cy.get('input[name="username"]').type('testuser');
    cy.get('input[name="password"]').type('qwerty');
    cy.get('button[type="submit"]').click();
    cy.url().should('include', '/dashboard');
  });
});

Набор тестов проверяет как негативные, так и позитивные сценарии входа в систему.

Тестирование списка задач

Проверим добавление, удаление и валидацию элементов списка:

describe('Todo List Functionality', () => {
  beforeEach(() => {
    cy.visit('/todos');
  });

  it('displays the list of todos correctly', () => {
    cy.get('li').should('have.length', 2); // предположим, что уже есть два элемента в списке
  });

  it('adds a new todo', () => {
    const newItem = 'Complete the test';
    cy.get('.new-todo-input').type(`${newItem}{enter}`);
    cy.get('li').should('have.length', 3);
  });

  it('deletes a todo', () => {
    cy.get('li').first().find('.delete-button').click();
    cy.get('li').should('have.length', 1);
  });
});

Различные плагины

Cypress не только позволяет тестировать приложение, но и расширяет его возможности через плагины, рассмотрим мои любимые.

cypress-axe

Плагин cypress-axe интегрирует инструменты доступности axe-core с Cypress, позволяя тестировать веб-страницы на предмет соответствия стандартам доступности:

it('Tests accessibility on the page', () => {
  cy.visit('/your-page');
  cy.injectAxe();
  cy.checkA11y();
});

cypress-plugin-tab

Плагин добавляет поддержку событий клавиатуры, к примеру нажатие на клавишу Tab, что не поддерживается в базовой версии Cypress:

it('should be able to navigate fields with tab', () => {
  cy.get('#first-input').type('info').tab().type('more info');
});

cypress-graphql-mock

Плагин cypress-graphql-mock позволяет мокать ответы GraphQL, что хорошо при разработке и тестировании клиентских приложений, которые используют GraphQL для взаимодействия с сервером:

it('mocks GraphQL data', () => {
  cy.mockGraphQL({ Query: { user: () => ({ id: 1, name: 'Artem' }) } });
  cy.visit('/user-profile');
  cy.contains('Artem');
});

Тест проверяет, что пользовательский интерфейс корректно отображает данные пользователя, мокая GraphQL запросы.

cypress-downloadfile

Плагин cypress-downloadfile добавляет возможность скачивания файлов во время выполнения тестов:

it('downloads a file', () => {
  cy.downloadFile('http://example.com/file.pdf', 'mydownloads', 'example.pdf');
  cy.readFile('mydownloads/example.pdf').should('exist');
});

Здесь скачивается файл PDF с предоставленного URL, после он сохраняется в директорию и далее проверяет наличие файла в системе.

cypress-localstorage-commands

Плагин cypress-localstorage-commands добавляет команды для управления данными в localStorage во время тестирования:

beforeEach(() => {
  cy.restoreLocalStorage();
});

afterEach(() => {
  cy.saveLocalStorage();
});

it('tests local storage', () => {
  cy.setLocalStorage('testKey', 'testValue');
  cy.getLocalStorage('testKey').should('eq', 'testValue');
});

Здесь перед каждым тестом восстанавливается состояние localStorage, а после теста — сохраняется.

Статья подготовлена в преддверии старта курса React.js Developer. В рамках запуска курса пройдут бесплатные вебинары, на которые могут зарегистрироваться все желающие:

© Habrahabr.ru