Вы когда-нибудь сталкивались с ситуацией, когда ваш компонент идеально выглядел в браузере, но тесты падали, потому что кто-то поменял класс или переименовал кнопку? Это не редкость. Часто мы пишем тесты, которые проверяют внутреннюю структуру компонента - его пропсы, состояние, даже порядок вызова хуков. Но если вы меняете реализацию, а тесты ломаются - значит, вы тестируете не то, что важно. Главное - чтобы пользователь мог использовать интерфейс. А не чтобы код выглядел как вы задумали.
Почему React Testing Library - это не просто ещё один инструмент
React Testing Library (RTL) не пытается имитировать React. Он пытается имитировать пользователя. Вместо того чтобы искать элемент по data-testid или проверять, что компонент вернул <div className="btn-primary">, RTL говорит: «Найди кнопку по тексту, который видит пользователь». Если кнопка называется «Отправить форму» - ищите её по этому тексту. Именно так пользователь её и найдёт.
Вот как выглядит типичный тест на RTL:
import { render, screen, userEvent } from '@testing-library/react';
import Button from './Button';
test('кнопка отправляет форму', async () => {
render(<Button>Отправить</Button>);
const button = screen.getByText('Отправить');
await userEvent.click(button);
expect(button).toBeDisabled();
});
Здесь нет никаких ссылок на внутреннюю логику. Нет findComponentByName, нет проверки пропсов. Только DOM, только действия, только результат. И это делает тесты устойчивыми к рефакторингу. Если вы решите заменить <button> на <div role="button"> - тест не сломается, пока текст останется тем же.
RTL использует screen для доступа к элементам, а userEvent для симуляции реальных действий: кликов, ввода текста, наведений. Это не просто вызов onClick() - это настоящие события браузера, как если бы их запустил человек. И да, userEvent работает с клавиатурой, с табуляцией, с анимациями. Он ждёт, пока элемент не станет доступным, не исчезнет с анимацией, не перерисуется после fetch.
Для улучшения сообщений об ошибках используется @testing-library/jest-dom. Без него вы получите expect(button).toBeDisabled() → «ожидалось, что будет disabled, но получил true». С ним - «кнопка «Отправить» не отключена, хотя должна была быть». Это разница между «что-то сломалось» и «вот где именно».
Vue: что есть вместо React Testing Library?
В экосистеме Vue нет прямого аналога React Testing Library. Но есть Vue Test Utils (VTU) - официальная библиотека для тестирования компонентов Vue. Она не так минималистична, как RTL, но даёт больше контроля. VTU позволяет рендерить компоненты, манипулировать пропсами, проверять состояние, эмулировать события. Но она не настаивает на том, чтобы тесты были «как пользователь» - это ваша ответственность.
Пример теста на VTU:
import { mount } from '@vue/test-utils';
import Button from './Button.vue';
test('кнопка отправляет форму', async () => {
const wrapper = mount(Button, {
props: { label: 'Отправить' }
});
await wrapper.find('button').trigger('click');
expect(wrapper.find('button').attributes('disabled')).toBe('disabled');
});
Здесь вы явно ищете кнопку по селектору button и вызываете trigger('click'). Это работает, но если вы замените <button> на <span> - тест сломается. В RTL такого бы не случилось, потому что вы искали по тексту.
Чтобы сделать тесты на Vue более «пользовательскими», разработчики часто комбинируют VTU с @testing-library/vue - это адаптация RTL для Vue. Она позволяет использовать те же методы: getByText, findByRole, userEvent. Но это не официальное решение, и она не всегда синхронизируется с последними версиями Vue.
Альтернатива - Vitest. Это быстрый, современный тестовый фреймворк, созданный авторами Vite. Он работает как Jest, но быстрее, с поддержкой ES Modules, без Webpack. Vitest отлично сочетается с VTU и @testing-library/vue. Многие команды Vue переходят на Vitest + VTU, потому что это даёт:
- Скорость: запуск тестов за секунды
- Совместимость с Vite
- Поддержку моков через
vi.mock() - Возможность писать тесты как на RTL, но с гибкостью Vue
Что выбрать: RTL для React или VTU для Vue?
Если вы работаете с React - используйте React Testing Library. Это не просто инструмент, это философия: тестируй интерфейс, а не реализацию. Она учит вас писать тесты, которые не ломаются при рефакторинге. Вы перестаёте думать о том, как компонент устроен, и начинаете думать о том, как он работает.
Если вы работаете с Vue - начните с VitEST + Vue Test Utils. Это современный, быстрый и гибкий стек. Не пытайтесь жестко копировать RTL. Вместо этого адаптируйте его подход: ищите элементы по тексту, используйте role и aria-label, пишите тесты, как будто вы пользователь. Можно использовать @testing-library/vue, если вам важна синтаксическая близость к RTL.
Вот простое сравнение:
| Критерий | React Testing Library | Vue Test Utils | Vitest |
|---|---|---|---|
| Основной принцип | Тестируй как пользователь | Тестируй компонент как модуль | Быстрый тестовый раннер |
| Поиск элементов | По тексту, роли, атрибутам | По селекторам, именам компонентов | Зависит от интеграции |
| Симуляция событий | userEvent (реальные браузерные события) |
trigger() (синтетические события) |
Поддерживает оба подхода |
| Скорость запуска | Средняя | Средняя | Очень высокая |
| Рекомендуемый для | React-проектов | Vue-проектов | Все проекты на Vite |
Что делать, если вы перешли с React на Vue?
Если ваша команда привыкла к RTL, переход на Vue может показаться странным. Но вы не теряете подход - вы просто меняете инструменты. Вот как адаптировать ваш стиль:
- Не используйте
data-testidкак основной способ поиска. Это вредит тестам. - Всегда добавляйте семантические атрибуты:
role="button",aria-label="Удалить",title="Открыть меню". - Ищите элементы по тексту:
getByText('Удалить'),findByRole('button', { name: 'Удалить' }). - Используйте
@testing-library/vue- это почти дубль RTL для Vue. - Замените Jest на Vitest - вы получите в 3-5 раз быстрее тесты.
Конечно, в Vue нет 100% копии RTL. Но вы можете добиться того же результата - тестов, которые не ломаются при рефакторинге, потому что они проверяют поведение, а не реализацию.
Почему это важно на практике?
Представьте, что вы работаете в команде, где каждый месяц кто-то меняет дизайн. Компонент кнопки переписали: теперь он использует <button> вместо <span>, добавили анимацию, перенесли логику в хук. Старые тесты - падают. Вы тратите день на их исправление. А потом понимаете: вы тестируете не кнопку. Вы тестируете то, как она была написана вчера.
С RTL и правильным подходом к Vue - вы тестируете кнопку как функцию. «Она должна быть кликабельной. После клика - отключаться. Должна быть доступна по клавише Enter». Это не зависит от того, как вы её реализовали. Это зависит от того, что пользователь ожидает.
Именно поэтому тесты, написанные по этим принципам, становятся не просто инструментом проверки - они становятся документацией. Они показывают: «вот как должен работать этот элемент». А не: «вот как он был написан».
Что ещё нужно знать?
- Не используйте
shallowMountв Vue Test Utils. Это ведёт к тестам, которые не проверяют реальное поведение. - Всегда тестируйте компоненты в реальном DOM. Не в упрощённой среде.
- Мокайте только внешние зависимости: API, хранилища, сторонние библиотеки.
- Используйте
waitForилиfindByдля асинхронных действий - неsetTimeout. - Пишите тесты до кода. Это не TDD - это просто логика: «что пользователь должен сделать?» - потом пишете код, чтобы это сработало.
Тестирование фронтенда - это не про то, чтобы покрыть 100% кода. Это про то, чтобы быть уверенным, что пользователь не столкнётся с багом. И если ваш тест не говорит вам этого - он бесполезен.
Можно ли использовать React Testing Library для Vue?
Нет, React Testing Library работает только с React. Для Vue есть отдельные инструменты: Vue Test Utils и @testing-library/vue. Последний - это адаптация RTL для Vue, которая позволяет использовать те же методы поиска и взаимодействия, что и в React. Но это не официальная библиотека Vue, и она может не поддерживать все новые функции Vue.
Почему в React Testing Library нет методов для проверки пропсов?
Потому что пользователь не видит пропсы. Он не знает, что компонент получил variant="primary" или isLoading={true}. Он видит кнопку, текст, поведение. Если вы тестируете пропсы - вы тестируете внутреннюю реализацию, а не функциональность. Это делает тесты хрупкими. RTL фокусируется на том, что видит пользователь - и только на этом.
Что лучше: Jest или Vitest для тестирования Vue?
Vitest - современный выбор. Он быстрее, работает с ES Modules, не требует Webpack, идеально интегрируется с Vite. Jest - надёжный, но тяжёлый. Если вы используете Vite в проекте - Vitest даст вам в 3-5 раз быстрее запуск тестов. Jest всё ещё поддерживается, но новые проекты переходят на Vitest.
Нужно ли писать тесты для всех компонентов?
Нет. Тестируйте только то, что имеет поведение: кнопки, формы, модальные окна, навигацию. Простые компоненты, которые просто отображают текст или стили - не требуют тестов. Не тратьте время на тесты, которые не защищают пользователя. Лучше 5 качественных тестов, чем 50 бесполезных.
Как начать тестировать, если у меня нет тестов вообще?
Начните с одного критического компонента - например, формы входа. Напишите три теста: 1) пользователь вводит email и пароль, 2) нажимает «Войти», 3) кнопка отключается. Используйте getByText и userEvent. Потом добавьте ещё один - например, проверку ошибки при неверном пароле. Постепенно вы поймёте, как писать тесты, которые не ломаются. Главное - начать.