JS: Dom Testing Library

Теория: Появление и Пропадание

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

Как это часто бывает, то что хорошо пользователям, усложняет жизнь программистам. Крестики-нолики, которые мы тестируем, умеют взаимодействовать с бекендом. Если в конструктор класса TicTacToe передать адрес, то после заполнения формы с именами и выявления победителя, игра будет отправлять информацию об этом по указанному адресу.

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

  // Заполняем document.body
  // Игра "разворачивает" себя в переданный элемент
  // Вызов метода start вешает обработчики
  const game = new TicTacToe(document.body, 'https://somedomain.com')
  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!')

  // Оставшаяся часть теста
})

Как только мы сделаем это изменение, то наш код сразу завершиться с ошибкой.

FAIL  __tests__/main.spec.ts > check game
Error: expect(element).toHaveTextContent()

Expected element to have text content:
  user 1, you are up!
Received:
  Tic Tac Toe Player 1 Player 2 Clear Board
 ❯ __tests__/main.spec.ts:23:25
     21|   await user.click(submitButton)
     22|
     23|   expect(document.body).toHaveTextContent('user 1, you are up!')

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

Подобная ситуация, в реальных приложениях встречается на каждом шагу, поэтому для нее есть стандартное решение. Оно сводится к тому, что мы не просто проверяем изменение, а мы выполняем постоянный опрос "появилось ли?". Если точнее, это делаем не мы сами, а наш тестовый фреймворк. В Testing Library для этого используется функция waitFor():

import { waitFor } from '@testing-library/dom'

await waitFor(() => {
  expect(document.body).toHaveTextContent('user 1, you are up!')
})

Функция waitFor() запускает проверки каждые 50 миллисекунд (interval) в течении одной секунды (timeout). Если утверждение так и не выполнится, то возникнет ошибка. Обычно 1 секунды должно хватать для любого действия в тесте. Если это не так, то нужно разбираться.

waitFor() проверяет именно выполнение матчеров (expect()), для других задач, например поиска элементов, используются другие механизмы.

findby && findAllBy

Ожидания бывают нужны не только для проверок, но и при поиске элементов, после асинхронных действий. В этом случае вместо getBy и getAllBy используются соответственно findBy и findAllBy. В остальном их поведение идентично.

// Пример кнопки, которая обновляет свой текст после каждого клика асинхронно
const user = userEvent.setup()
const button = screen.getByRole('button', { name: 'Click Me' })
user.click(button)
await screen.findByText('Clicked once')
user.click(button)
await screen.findByText('Clicked twice')

Завершено

0 / 7