Начал разбираться с автоматическим тестированием фронтенда. Тема оказалось сложной. Гугл выдает огромное число разнообразных библиотек и фреймворков для тестирования, причем не понятно, какие из них взаимозаменяемы, а какие нужно использовать вместе для разных целей. Пока что научился писать простейшие тесты при помощи двух библиотек: jest и react-testing-library.

Для примера приведу тесты для проверки работоспособности псевдоссылок для входа и регистрации в моем чатике.

В шапке есть две ссылки: Login и Sign up. Логика работы их такова:

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

Вот полностью код, который проверяет, действительно ли приложение работает, как описано выше:

import React from 'react';
import { Simulate, render } from 'react-testing-library';
import { Provider } from 'react-redux';
import { createStore } from 'redux';

import reducer from '../src/client/reducers';
import Main from '../src/client/components/Main.jsx';

const store = createStore(reducer);

test('Renders signup and signin forms', () => {
  const { getByText, container } =
    render(<Provider store={store}><Main /></Provider>);

  expect(container.firstChild).toMatchSnapshot();

  Simulate.click(getByText('Login'));
  expect(container.firstChild).toMatchSnapshot();

  Simulate.click(getByText('Sign up'));
  expect(container.firstChild).toMatchSnapshot();

  Simulate.click(getByText('Sign up'));
  expect(container.firstChild).toMatchSnapshot();
});

Работает он так:

1. Рендерим реакт-компонент при помощи render из react-testing-library. В результате извлекаем ДОМ-объект container и вспомогательную функцию getByText, она пригодится чтобы находить ноды с нужным текстом.

2. Проверяем, что получившийся ДОМ соответствует образцу, хранящемуся в снепшоте. Снепшот — это состояние дерева, которое строит реакт для рендеринга приложения. При первом запуске тестов строка expect(container.firstChild).toMatchSnapshot() создает и сохраняет файл со снепшотом в папку __snapshots__, а при каждом следующем запуске сравнивает этот сохраненный снепшот с новым и смотрит, что поменялось. Если ничего не поменялось, то тест считается пройденным. Если поменялось, то тест падает, выводит разницу между снепшотами и предлагает обновить снепшот, если изменение преднамеренное.

Вот кусочек первого снепшота, который показывает форму входа:

<div
    class="loginForm"
    id="loginForm"
    style="display: none;"
  >
    <form>
      <label
        class="loginForm__label"
      >
        Username: 
        <input
          id="loginUsername"
          type="text"
          value=""
        />
        <br />
      </label>
      <label
        class="loginForm__label"
      >
        Password: 
        <input
          id="loginPassword"
          type="password"
          value=""
        />
        <br />
      </label>
      <button
        class="btn btn--secondary"
        type="submit"
      >
        Login
      </button>
    </form>
    <span
      class="error"
    />
  </div>

Обратите внимание на стиль дива display: none. Изначально форма должна быть спрятана, поэтому пока все идет как и запланировано.

3. Дальше мы находим ссылки, работу которых пытаемся проверить, имитируем клики по ним, и проверяем, что приложение перерендерилось так, как было задумано.

Simulate.click(getByText('Login'));
  expect(container.firstChild).toMatchSnapshot();

Если после нажатия ссылки форма не появляется, то тест выведет примерно такую ошибку:

FAIL  __tests__/test.jsx
  ● Renders signup and signin forms

    expect(value).toMatchSnapshot()

    Received value does not match stored snapshot "Renders signup and signin forms 2".

    - Snapshot
    + Received

    -     style="display: block;"
    +     style="display: none;"

Минуc такого метода со снепшотами в том, что им нельзя тестировать по TDD, когда ты сначала пишешь тесты, а потом удовлетворяющий этим тестам код. Тут нужно сначала написать код, удостовериться каким-то другим способом, что он работает, и только потом можно будет поснимать с него снепшоты. То есть подход помогает удостовериться, что в процессе последующей работы ничего не сломается, но не помогает написать первоначальную реализацию.

Далее предстоит разобраться в том, как писать тесты посложнее: например, как проверить работу риал-тайм приложения с сокетами, и как протестировать фронтенд с бекендом вместе.