JS: Dom Testing Library

Теория: Введение

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

// Пример теста на Playwright
import { test, expect } from '@playwright/test'

test('load users button functionality', async ({ page }) => {
  await page.goto('https://http.hexlet.app/js-playwright/users-list')

  // Нажимаем на кнопку для загрузки пользователей
  await page.click('#loadDataBtn')

  // Ожидаем, что пользователи загрузятся и отобразятся на странице
  const userNames = await page.getByRole('listitem').allTextContents()
})

Подобные тесты довольно дороги в написании и сопровождении, поэтому в большинстве проектов, их пишут специальные люди, автоматизаторы e2e-тестов. Разработка не занимается такими тестами и двигается быстро вперед. Она соприкасается с этими тестами только тогда, когда тестировщики натыкаются на баги и создают для них тикеты.

В чем заключается дороговизна этих тестов? По пунктам:

  • Запуск реального браузера, заполнение форм, прокликивание и анимация, все это требует больших ресурсов и происходит медленно. e2e-тесты легко могут идти часами даже на не очень больших проектах.
  • Эти тесты хрупкие. В них постоянно что-то ломается из-за асинхронного поведения. Поэтому если тест сломался, еще не факт что это баг. Нужно сидеть и копать. Вполне возможно, что ошибка пропадет при повторном запуске.

По крайней мере так было до момента появления jsdom, библиотеки написанной на JavaScript для упрощенного e2e-тестирования. Она реализует подмножество функций браузера, необходимых для отрисовки и взаимодействия со страницей без ее визуального представления. С ее помощью можно писать тесты приближенные к классическим e2e-тестам, получив, при этом, несколько важных плюсов:

  • Эта библиотека внедрена по умолчанию в популярные тестовые фреймворки (Jest и Vitest). Если они есть в проекте, то писать такие тесты можно начать прямо сейчас.
  • Из-за отсутствия визуальной отрисовки, скорость работы таких тестов значительно выше и они потребляют намного меньше ресурсов.
  • Тесты запускаются не во внешнем браузере, а прямо в том месте где они написаны (в том же процессе). Это на многое влияет, но поговорим мы об этом во время написания этих тестов

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

// Пример из документации https://jestjs.io/docs/tutorial-jquery

jest.mock('../fetchCurrentUser')

test('displays a user after a click', () => {
  // Set up our document body
  document.body.innerHTML
    = '<div>'
      + '  <span id="username" />'
      + '  <button id="button" />'
      + '</div>'

  // This module has a side-effect
  require('../displayUser')

  const $ = require('jquery')
  const fetchCurrentUser = require('../fetchCurrentUser')

  fetchCurrentUser.mockImplementation((cb) => {
    cb({
      fullName: 'Johnny Cash',
      loggedIn: true,
    })
  })

  $('#button').click()

  expect(fetchCurrentUser).toHaveBeenCalled()
  expect($('#username').text()).toBe('Johnny Cash - Logged In')
})

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

Несмотря на интеграцию, писать тесты в таком стиле было бы неудобно, потому что ни jsdom ни тестовые фреймворки не предлагают удобных механизмов для тестирования страниц. Только чистый DOM API. В первую очередь это влияет на отладку. Как посмотреть с чем мы работаем? Как подождать появление элемента на странице? Как заполнить поле в форме? Как проверить видимость элемента? И так далее.

Для всего этого, нам понадобится библиотека тестирования, которая называется Testing Library. Она предоставляет удобное API для поиска элементов, имитации пользовательских событий, отладки и полезных функций для, например, работы с асинхронными вызовами. Пример из документации:

import {
  getByLabelText,
  getByText,
  getByTestId,
  queryByTestId,
  waitFor,
} from '@testing-library/dom'
import '@testing-library/jest-dom'

function getExampleDOM() {
  const div = document.createElement('div')
  // Наполнение DOM
  return div
}

test('examples of some things', async () => {
  const famousProgrammerInHistory = 'Ada Lovelace'
  const container = getExampleDOM()

  const input = getByLabelText(container, 'Username')
  input.value = famousProgrammerInHistory

  getByText(container, 'Print Username').click()

  await waitFor(() =>
    expect(queryByTestId(container, 'printed-username')).toBeTruthy(),
  )

  expect(getByTestId(container, 'printed-username')).toHaveTextContent(
    famousProgrammerInHistory,
  )
  expect(container).toMatchSnapshot()
})

Помимо прочего, эта библиотека устроена таким образом, что она не завязывается на то, как конкретно реализована страница и какой фреймворк на ней используется. И в отличие от классических e2e-тестов, тесты с ее использованием могут без проблем писать как автоматизаторы, так и разработчики. Это точно имеет смысл для особо критичных участков.

В этом курсе мы научимся писать e2e-тесты используя Testing Library. Изучим подходы, лучшие практики и эффективную отладку, что очень важно в таких тестах. Поехали.

Завершено

0 / 7