- Dom Testing Library
- Экосистема Testing Library
- Ключевой руководящий принцип
- Запросы
- ByRole
- ByText
- TextMatch
- ByTestId
- Вызов событий
- Асинхронность
- waitFor
- Рефакторинг
- user-event
- user-event API
- Click
theme: gaia class:
- lead
- invert paginate: true ---
Dom Testing Library
Hexlet
Экосистема Testing Library
dom testing library— основная библиотекаuser-event— имитация браузерных событийjest-dom— кастомные матчеры Jesteslint-plugin-testing-library— плагин ESLint для Testing Libraryeslint-plugin-jest-dom— плагин ESLint для Jest DOM- Версии для фреймворков
react testing libraryangular testing librarysvelte testing library
Ключевой руководящий принцип
Чем лучше ваши тесты имитируют реальное использование вашего приложения, тем больше уверенности они могут вам дать
- тестируйте UI компоненты с точки зрения пользователя
- избегайте проверки деталей реализации
- не полагайтесь на конкретный фреймворк для тестирования
- не используйте test runner
DOM Testing Libraryработает с любым окружением, где есть DOM API- Jest, Mocha
- JSDOM
- реальный браузер
test('greeting', () => {
const h1 = queryByText(/Hello/i)
expect(h1).toHaveTextContent('Hello username!')
expect(queryByText('Goodbye!')).not.toBeInTheDocument()
})
Запросы
- get, getAll
- query, queryAll
- find, findAll
| No Match | 1 Match | 1+ Match | Await? | |
|---|---|---|---|---|
| getBy | throw | return | throw | No |
| findBy | throw | return | throw | Yes |
| queryBy | null | return | throw | No |
| getAllBy | throw | array | array | No |
| findAllBy | throw | array | array | Yes |
| queryAllBy | [] | array | array | No |
ByRole
- Селектор элементов по роли
getByRole,getAllByRolequeryByRolequeryAllByRolefindByRole,findAllByRole- Доступные роли: link, button, form, heading, document, img, checkbox, radio, listitem, main, navigation, table, textbox
getByRole(expectedRole, { name: /submit/i })getByRole('checkbox', { checked: true })getAllByRole('button', { hidden: true })
test('show a required field warning', async () => {
const loginButton = getByRole(container, 'button', { name: 'Login' })
loginButton.click()
expect(await findByText(container, 'User Name Required')).toBeVisible()
expect(await findByText(container, 'Password Required')).toBeVisible()
})
ByText
<a href="/about">About ℹ️</a>
import { getByText } from '@testing-library/dom'
const aboutLinkNode = getByText(document.body, /about/i)
// ИЛИ
import { screen } from '@testing-library/dom'
const aboutLinkNode = screen.getByText(/about/i)
TextMatch
- как строка
screen.getByText("Hello World")- поиск по полной строкеscreen.getByText('llo worl', { exact: false })— поиск по подстроке, игнорируется регистр
- как регулярное выражение
screen.getByText(/world/i)— поиск по подстроке, игнорируется регистрscreen.getByText(/^hello world$/i)— поиск по полной строке, игнорируется регистр
- как функция
screen.getByText((content, element) => content.startsWith("Hello"))
screen.getByLabelText('Username')ищет элемент с соответствющим labelgetByLabelTextхорошо подходит для полей формыgetByPlaceholderTextищет по атрибуту placeholdergetByTitleищет по атрибуту title
<div id="app">
<label for="username-input">Username</label>
<input id="username-input" />
</div>
import { getByLabelText } from '@testing-library/dom'
const container = document.querySelector('#app')
const inputNode = getByLabelText(container, 'Username')
// ИЛИ
import { screen } from '@testing-library/dom'
const inputNode = screen.getByLabelText('Username')
ByTestId
<div data-testid="mailbox" />
const element = document.body.querySelector(`[data-testid="mailbox"]`)
// OR
import { screen } from '@testing-library/dom'
const element = screen.getByTestId('mailbox')
configure({testIdAttribute: 'data-my-test-attribute'})
Расширение для браузеров Chrome и Firefox Testing Playground
Вызов событий
fireEvent(node: HTMLElement, event: Event)
<button>Submit</button>
fireEvent(
screen.getByText('Submit'),
new MouseEvent('click', {
bubbles: true,
cancelable: true,
}),
)
fireEvent[eventName](node: HTMLElement, eventProperties: Object)
fireEvent.change(getByPlaceholderText(/username/i), { target: { value: 'ruslan' } })
fireEvent.keyDown(getByRole('button'), { key: 'Enter', code: 'Enter' })
Асинхронность
const button = screen.getByRole('button', { name: 'Login' })
fireEvent.click(button)
await screen.findByText('Hello')
findBy = getBy + waitFor
waitFor
function waitFor<T>(
callback: () => T | Promise<T>,
options?: {
container?: HTMLElement // document по умолчанию
timeout?: number // 1000мс по умолчанию
interval?: number // 50мс по умолчанию
onTimeout?: (error: Error) => Error // ошибка и состояние контейнера по умолчанию
mutationObserverOptions?: MutationObserverInit // вызов колбека при изменениях
}
): Promise<T>
await waitFor(() => screen.getByRole('alert'))await waitFor(() => expect(mockAPI).toHaveBeenCalledTimes(1))WaitForElementToBeRemoved
wrapper on
waitFor
const button = document.querySelector('button');
waitForElementToBeRemoved(document.querySelector('button')).then(() =>
console.log('Element button no longer in DOM');
);
// изменения игнорируются
button.setAttribute('data-is-submit', true);
// логирует строку 'Element button no longer in DOM'
button.parentElement.removeChild(button);
fireEventподходит для большинства сценариев, НОfireEvent.clickне порождает другие события:fireEvent.mouseOver(element)fireEvent.mouseMove(element)fireEvent.mouseDown(element)element.focus()(если элемент допускает это)fireEvent.mouseUp(element)fireEvent.click(element)
Рефакторинг
// Было
fireEvent.focus(getByText('focus me'))
// Стало
getByText('focus me').focus()
user-event
Это вспомогательная библиотека для Testing Library, которая обеспечивает более совершенное моделирование взаимодействия с браузером, чем встроенный метод fireEvent
test('types inside textarea', () => {
document.body.innerHTML = `<textarea />`
userEvent.type(screen.getByRole('textbox'), 'Hello, World!')
expect(screen.getByRole('textbox')).toHaveValue('Hello, World!')
})
test('click', () => {
render(`
<div>
<label for="checkbox">Check</label>
<input id="checkbox" type="checkbox" />
</div>
`)
userEvent.click(screen.getByText('Check'))
expect(screen.getByLabelText('Check')).toBeChecked()
})
user-event API
- click / dbclick
- hover / unhover
- type
- upload
- selectOptions / deselectOptions
- tab
- paste
Click
click(element, eventInit, options)
Клики по элементу, могут иметь различные побочные эффекты в зависимости от элемента
<div>
<label htmlFor="checkbox">Check</label>
<input id="checkbox" type="checkbox" />
</div>
userEvent.click(screen.getByText('Check'))
expect(screen.getByLabelText('Check')).toBeChecked()
- Клик вызовет сначала событие hover
Его можно отключить установив
trueдля параметраskipHover - Можно также вызывать клик с зажатыми клавишаи, ctrlClick / shiftClick / пр.
userEvent.click(elem, { ctrlKey: true, shiftKey: true })
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.