- 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 library
angular testing library
svelte 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
,getAllByRole
queryByRole
queryAllByRole
findByRole
,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 })
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.