theme: gaia class:
dom testing library
— основная библиотекаuser-event
— имитация браузерных событийjest-dom
— кастомные матчеры Jesteslint-plugin-testing-library
— плагин ESLint для Testing Libraryeslint-plugin-jest-dom
— плагин ESLint для Jest DOMreact testing library
angular testing library
svelte testing library
Чем лучше ваши тесты имитируют реальное использование вашего приложения, тем больше уверенности они могут вам дать
DOM Testing Library
работает с любым окружением, где есть DOM API
test("greeting", () => {
const h1 = queryByText(/Hello/i);
expect(h1).toHaveTextContent("Hello username!");
expect(queryByText("Goodbye!")).not.toBeInTheDocument();
});
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 |
getByRole
, getAllByRole
queryByRole
queryAllByRole
findByRole
, findAllByRole
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();
});
<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);
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");
<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
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();
Это вспомогательная библиотека для 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();
});
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();
true
для параметра skipHover
userEvent.click(elem, { ctrlKey: true, shiftKey: true })
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт