JS: Dom Testing Library

Теория: Рабочий процесс

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

Тестовый проект

В качестве проекта для тестирования мы будем использовать классическую игру крестики-нолики. Посмотреть как она работает можно по ссылке.

Игра состоит из двух экранов. На первом экране вводятся имена пользователей.

Крестики-нолики JS (Имена игроков)

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

Крестики-нолики JS

Игра реализована в виде библиотеки @hexlet/tic-tac-toe, которую можно установить и протестировать. Именно это мы и сделаем. Выполните установку пакета в нашем репозитории:

npm i @hexlet/tic-tac-toe

План теста

Тесты на jsdom формируются следующим образом:

  1. Заполняется document.body в текущем контексте теста. Это и будет страница, которую мы тестируем.
  2. Выбираются нужные элементы, над которыми планируется производить действия.
  3. Выполняются необходимые действия: клики, заполнение форм и т.п.
  4. Выполняются необходимые проверки, например, наличие определенного текста на странице
  5. Если нужно шаги повторяются

Тест

Тесты добавляются в файлы *.spec.js в директории ___tests___. Ниже пример такого теста:

// __tests__/main.spec.yml

// Подключаем стили для vitest-preview
import '@hexlet/tic-tac-toe/public/style.css'
import { test, expect } from 'vitest'
import { screen } from '@testing-library/dom'
import userEvent from '@testing-library/user-event'

// Импортируем игру
import { TicTacToe } from '@hexlet/tic-tac-toe'

test('main', async () => {
  // Объект для выполнения действий над элементами
  const user = userEvent.setup()

  // Заполняем document.body
  // Игра "разворачивает" себя в переданный элемент
  // Вызов метода start вешает обработчики
  const game = new TicTacToe(document.body)
  game.start()

  // Выбираем нужные элементы
  // Находим поля для ввода имен игроков
  const input1 = screen.getByLabelText('Player 1')
  const input2 = screen.getByLabelText('Player 2')

  // Выполняем необходимые действия
  // Вводим имена пользователей
  await user.type(input1, 'user 1')
  await user.type(input2, 'user 2')

  // Отправляем форму
  const submitButton = screen.getByText('Start Game')
  await user.click(submitButton)

  // Проверяем результат
  expect(document.body).toHaveTextContent('user 1, you are up!')
})

В этом тесте мы видим полный путь от заполнения документа, до проверки результата после всех манипуляций. Сам тест достаточно хорошо читается. Этим e2e-тесты привлекательны, по ним видны сценарии использования.

Обратите внимание на несколько важных моментов:

  • Тесты выполняются внутри окружения jsdom, поэтому сама страница доступна нам как глобальный объект document
  • Взаимодействие с этим документом выполняется с помощью объекта screen, который содержит нужные методы для запросов
  • События порождаются через объект user, а не прямое управление элементами DOM. Так сделано, потому что поведение пользователя и DOM событие не всегда одно и тоже. Пользовательские события более высокоуровневые. Подробнее об этом мы поговорим в следующих уроках.
  • События асинхронны, поэтому перед методами событий используется await

Как видно по коду теста, для его написания нам нужно видеть страницу, элементы которые на ней расположены и тексты. Это можно сделать запустив тестируемый проект рядом и постоянно посматривая туда. Но есть и другой способ, о котором мы поговорим ниже.

Запуск тестов

Запустить тест на выполнение можно двумя способами. Первый, одноразовый запуск после которого тест завершается.

npx vitest run

В режиме разработки удобнее пользоваться вторым способом, выполняемым в режиме watch, когда тесты запускаются автоматически при каждом изменении:

npx vitest

Эта команда захватит управление в терминале до тех пор, пока мы не выполним ctrl + c.

Отладка

Одна из главных сложностей при написании e2e-тестов это отладка. Например, мы ищем какой-то элемент, но его не оказывается в документе. Или мы проверяем наличие текста, а текст отсутствует. Как понять почему так происходит? Testing Library всеми силами пытается в этом помочь. Во время запуска теста, она выводит в терминал максимум возможной информации включая куски HTML, соответствующие текущей ошибке.

Давайте, для примера, поменяем в тесте выше код screen.getByLabelText('Player 1'), на screen.getByLabelText('Player 3'). Текста Player 3 на странице нет и testing-library это увидит. Тесты остановятся на этом вызове с таким сообщением:

Тест упавший с ошибкой (Testing Library)

Подобный вывод еще можно прочитать если HTML небольшой, но на реальных страницах слишком много HTML, чтобы его вот так читать в терминале. Что делать в этом случае?

А тут нам поможет библиотека vitest-preview, которую мы установили в предыдущем уроке. С ее помощью мы можем посмотреть документ на любом этапе работы с ним в тесте. Для ее работы нужно выполнить два действия:

  1. Запустить команду в терминале. Она поднимет сервер, на котором можно будет посмотреть документ. Команда автоматически открывает вкладку в браузере.

    npx vitest-preview
  2. Добавить вызов функции debug(), которая отправит состояние документа на момент вызова в браузер. Обновление в браузера происходит только после запуска тестов, поэтому для работы vitest-preview удобно запускать тесты в режиме watch.

    // Импортируем библиотеку
    import { debug } from 'vitest-preview'
    test('main', async () => {
      const user = userEvent.setup()
    
      const game = new TicTacToe(document.body)
      game.start()
    
      debug() // вызов
    
      // Остальная часть теста
    })

Перемещая таким образом вызов функции debug() по тесту, мы будем видеть разные состояния страницы до или после каких-то действий. Чем раньше мы освоим этот способ отладки, тем быстрее мы сможем писать тесты.

Завершено

0 / 7