🔍🏃 С Vitest ваше тестирование в Vite станет легким и эффективным
Vitest – тест-раннер, который идеально интегрируется с Vite, делая процесс тестирования быстрым и интуитивно понятным.
Привет, друзья! Я Кирилл Мыльников, frontend-разработчик в компании Usetech. Сегодня хочу рассказать про такую классную вещь как Vitest.
Vitest – мощный тест-раннер, специально созданный для проектов Vite. С легкостью интегрируется в рабочий процесс Vite без необходимости сложной настройки. Это обеспечивает высокую эффективность для разработчиков, позволяя писать тесты без лишних забот. Оптимизированный для работы с Vite, Vitest использует современные функции JavaScript (ESM-импорт, ожидание верхнего уровня) для тестирования и предлагает чистый подход. Разработчики Vite могут легко создавать эффективные и действенные тесты, упрощая процесс и используя современные возможности.
Сравнение Runner с Jest для проектов Vite
Особенность | Vitest | Jest |
Собственная интеграция | Полная интеграция с принципами работы Vite | Требует дополнительной настройки |
Поддержка современного синтаксиса | Поддерживает импорт ESM, ожидание верхнего уровня | Ограниченная поддержка современного синтаксиса JavaScript |
Расширяемость | Можно расширять функциональность с помощью плагинов | Ограниченная возможность расширения |
Поддержка TypeScript | Легко интегрируется с TypeScript/JSX | Требуется дополнительная настройка |
Тестирование в исходном коде | Необязательно: размещайте тесты рядом с кодом | Не поддерживается |
Панель инструментов графического интерфейса | Дополнительно: Визуализация результатов тестов | Нет в наличии |
Обработка тестов | Автоматическое повторные попытки для нестабильных тестов | Требуется ручная установка или сторонние библиотеки |
Начало работы с Runner
В этом разделе вы узнаете, как настроить современный тестовый раннер для вашего проекта, используя простой пример. После настройки у вас будет несколько способов запустить тесты с использованием вашего кода. В данной статье мы рассмотрим два распространенных сценария тестирования: проверку базовых функций и тестирование вызовов API.
Тестирование основных функций
Давайте протестируем две базовые функции, например, операцию сложения двух чисел. Этот пример отлично подойдет для того, чтобы начать использовать тест-раннер, особенно если вы новичок в области тестирования или работаете над простыми проектами. Шаг за шагом мы создадим проект, установим тест-раннер, напишем необходимый скрипт и запустим его для проверки.
Настройка проекта Vite (необязательно). Если у вас еще нет проекта, вы можете быстро создать его с помощью официального интерфейса командной строки Vite:
npm init vite@latest my-vite-project cd my-vite-project
- Создайте новый проект с названием my-vite-project и настройками по умолчанию.
- Установите фреймворк: добавьте тест-раннер в качестве зависимости разработки, выполнив команду npm install.
npm install --save-dev vitest
3. Напишем базовые тесты: Создадим файл для тестов с именем math.test.js, в котором продемонстрируем написание тестов. Ниже приведен код:
// math.test.js // Import necessary functions for testing from Vitest import { expect, test } from 'vitest'; // Define a function named sum that takes two numbers (a and b) as arguments // and returns their sum function sum(a, b) { return a + b; } // Create a test using the `test` function from Vitest test('adds two numbers', () => { // Inside the test function, we define what we want to test // The first argument is a description of the test // Use the `expect` function to make assertions about the result // We expect the sum of 2 and 3 to be 5 expect(sum(2, 3)).toBe(5); });
Этот фрагмент кода определяет тест, чтобы убедиться, что sum функция правильно складывает два числа. Он импортирует необходимые функции тестирования, определяет sum функцию, которая складывает два числа и использует методы утверждения для проверки того, что sum(2, 3) возвращает 5.
4. Запуск тестов: чтобы запустить тест, выполните следующую команду в терминале:
npx vitest
Тестовый раннер выполнит тест math.test.js и выведет результаты. В случае успешного прохождения утверждения будет отображено сообщение об успехе. В случае неудачи вы увидите сообщение об ошибке, которое указывает на проблему.
Преимущество этого фреймворка заключается в его нативной интеграции с существующим конвейером сборки Vite. По умолчанию Vite автоматически распознает файлы тестов с расширениями .test.js или .spec.js. Vite запускает эти файлы тестов вместе с кодом вашего приложения во время процесса сборки. Это обеспечивает выполнение ваших тестов при сборке продакшн-версии приложения, что позволяет выявить потенциальные проблемы до развертывания.
Тестирование вызовов API
При работе с реальными приложениями важное значение имеет тестирование взаимодействия с API. Рассмотрим процесс тестирования различных операций API с помощью нашего тестового исполнителя. Для начала создадим комплексный файл userAPI.js, который будет включать несколько методов HTTP и обработку ошибок.
Сначала рассмотрим функцию getUser:
// userAPI.js async getUser(userId) { const response = await fetch(`https://api.example.com/users/${userId}`); if (!response.ok) { throw new Error('Failed to fetch user'); } return response.json(); }
Этот метод извлекает информацию о пользователе по его идентификатору. Он отправляет GET-запрос к конечной точке API, проверяет успешность ответа и возвращает распарсенные JSON-данные. В случае неположительного ответа, метод генерирует ошибку.
Далее рассмотрим функцию createUser:
// userAPI.js async createUser(userData) { const response = await fetch('https://api.example.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(userData) }); if (!response.ok) { throw new Error('Failed to create user'); } return response.json(); }
Этот метод создает нового пользователя. Он отправляет POST-запрос к конечной точке API с данными пользователя в теле запроса. Данные преобразуются в JSON-строку. В случае успешного ответа, метод возвращает информацию о новом пользователе. В случае неудачного ответа, метод генерирует ошибку.
Теперь рассмотрим функцию updateUser:
async updateUser(userId, userData) { const response = await fetch(`https://api.example.com/users/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(userData) }); if (!response.ok) { throw new Error('Failed to update user'); } return response.json(); }
Этот метод обновляет информацию о существующем пользователе. Он отправляет PUT-запрос в конечную точку API с обновленными данными пользователя в теле запроса. Если обновление прошло успешно, возвращаются обновленные данные пользователя. В случае неудачи, метод генерирует ошибку.
Наконец, рассмотрим функцию deleteUser:
async deleteUser(userId) { const response = await fetch(`https://api.example.com/users/${userId}`, { method: 'DELETE' }); if (!response.ok) { throw new Error('Failed to delete user'); } return true; }
В файле userAPI.js определяются функции взаимодействия с пользовательским API, включая операции по получению, созданию, обновлению и удалению пользователей.
Этот метод удаляет пользователя по его ID. Он отправляет DELETE-запрос в конечную точку API. Если удаление проходит успешно, возвращается значение true. В случае возникновения ошибки, метод генерирует соответствующее сообщение.
Давайте напишем комплексные тесты для этих методов API в файле api.test.js.
// api.test.js import { expect, test, vi } from 'vitest'; import { userAPI } from './userAPI'; // Mock the fetch function globally vi.mock('node-fetch'); const mockFetch = vi.fn(); global.fetch = mockFetch; test('getUser fetches user data successfully', async () => { const mockUser = { id: 1, name: 'John Doe' }; mockFetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockUser) }); const user = await userAPI.getUser(1); expect(user).toEqual(mockUser); expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/users/1'); });
Этот первый тестовый случай сосредоточен на функции getUser. Давайте его рассмотрим:
- В наших тестах мы имитируем функцию fetch, чтобы контролировать ее поведение.
- Мы настраиваем фиктивный объект пользователя, который должен возвращаться при вызове API.
- Мы используем mockFetch.mockResolvedValueOnce(), чтобы задать наши фиктивные данные пользователя и смоделировать успешный ответ от API.
Затем мы вызываем userAPI.getUser(1) и используем expect() для проверки того, что данные пользователя, полученные из функции, соответствуют нашему фиктивному пользователю, а также проверяем, что функция fetch была вызвана с правильным URL.
Давайте рассмотрим тест для функции createUser:
test('createUser sends POST request with user data', async () => { const newUser = { name: 'Jane Doe', email: 'jane@example.com' }; const mockResponse = { id: 2, ...newUser }; mockFetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockResponse) }); const createdUser = await userAPI.createUser(newUser); expect(createdUser).toEqual(mockResponse); expect(mockFetch).toHaveBeenCalledWith( 'https://api.example.com/users', expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newUser) }) ); });
Пояснение что делает тест createUser:
- Мы создаем фиктивный объект нового пользователя и фиктивный ответ.
- Мы моделируем успешный ответ от API при создании пользователя.
- Затем мы вызываем userAPI.createUser(newUser) и проверяем, что созданный пользователь соответствует нашему фиктивному ответу.
- Также мы проверяем, что функция fetch была вызвана с правильным URL, методом, заголовками и телом запроса.
Давайте перейдем к тесту функции updateUser:
// api.test.js test('updateUser sends PUT request with updated data', async () => { const userId = 1; const updatedData = { name: 'John Updated' }; const mockResponse = { id: userId, ...updatedData }; mockFetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockResponse) }); const updatedUser = await userAPI.updateUser(userId, updatedData); expect(updatedUser).toEqual(mockResponse); expect(mockFetch).toHaveBeenCalledWith( `https://api.example.com/users/${userId}`, expect.objectContaining({ method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatedData) }) ); });
Пояснение что делает тест updateUser:
- Устанавливает тестовые данные: идентификатор пользователя и обновленную информацию о нем.
- Создает фиктивный ответ, который обычно возвращает API.
- Настраивает функцию фиктивной выборки для возврата успешного ответа с фиктивными данными.
- Вызывает функцию updateUser с тестовыми данными.
- Проверяет, что данные, возвращаемые функцией, соответствуют ожидаемому фиктивному ответу.
- Проверяет, что функция fetch была вызвана с правильным URL, методом (PUT), заголовками и телом запроса.
Теперь давайте посмотрим на deleteUser тест:
test('deleteUser sends DELETE request', async () => { const userId = 1; mockFetch.mockResolvedValueOnce({ ok: true }); const result = await userAPI.deleteUser(userId); expect(result).toBe(true); expect(mockFetch).toHaveBeenCalledWith( `https://api.example.com/users/${userId}`, expect.objectContaining({ method: 'DELETE' }) ); });
Пояснение что делает тест deleteUser:
- Создает тестовый идентификатор пользователя.
- Настраивает функцию фиктивной выборки для возврата успешного ответа.
- Вызывает функцию deleteUser с тестовым идентификатором пользователя.
- Проверяет, что функция возвращает значение true, указывающее на успешное удаление.
- Наконец, проверяет, была ли вызвана функция fetch с правильным URL и методом (DELETE).
Наконец, давайте рассмотрим тесты обработки ошибок
test('getUser handles error response', async () => { mockFetch.mockResolvedValueOnce({ ok: false }); await expect(userAPI.getUser(1)).rejects.toThrow('Failed to fetch user'); }); // Additional error handling tests can be added for other API methods test('createUser handles error response', async () => { mockFetch.mockResolvedValueOnce({ ok: false }); await expect(userAPI.createUser({})).rejects.toThrow('Failed to create user'); }); test('updateUser handles error response', async () => { mockFetch.mockResolvedValueOnce({ ok: false }); await expect(userAPI.updateUser(1, {})).rejects.toThrow('Failed to update user'); }); test('deleteUser handles error response', async () => { mockFetch.mockResolvedValueOnce({ ok: false }); await expect(userAPI.deleteUser(1)).rejects.toThrow('Failed to delete user'); });
Эти тесты моделируют ответы API на ошибки и проверяют, что каждая функция возвращает ошибку с ожидаемым сообщением, если запрос был неуспешным.
На изображении представлен отчет о тестировании, связанном с API. Отчет показывает несколько успешно пройденных тестов, каждый из которых отмечен зеленой галочкой. Для каждого теста представлено описание, например, "getUser: успешное получение пользовательских данных" и "createUser: отправка POST запроса с пользовательскими данными". Внизу отчета указано общее количество выполненных тестов, все из которых успешно пройдены, а также общее время выполнения тестов.
Тестирование DOM с помощью Vitest
Этот инструмент тестирования обладает высокой мощностью в среде DOM-тестирования и способен имитировать все действия браузера, что значительно облегчает тестирование взаимодействия с пользователем. В данной статье мы рассмотрим шаги по настройке и запуску DOM-тестов.
Настройка jsdom
npm install --save-dev jsdom
Тестирование манипуляций DOM
Давайте создадим простую функцию для управления DOM и затем проведем тестирование ее работы.
// Function to create a greeting element and add it to the DOM export function createGreeting(name) { // Create a new div element const div = document.createElement('div'); // Set the text content of the div div.textContent = `Hello, ${name}!`; // Add a CSS class to the div div.className = 'greeting'; // Append the div to the document body document.body.appendChild(div); // Return the created element return div; }
Эта функция createGreeting создает новый элемент div, устанавливает его текстовое содержимое и класс, а затем добавляет его в тело документа.
Давайте напишем тест для этой функции.
// dom-utils.test.js import { expect, test, beforeEach } from 'vitest'; import { JSDOM } from 'jsdom'; import { createGreeting } from './dom-utils'; // Set up a fresh DOM before each test beforeEach(() => { // Create a new JSDOM instance with a basic HTML structure const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>'); // Set the global document and window objects to use the JSDOM instance global.document = dom.window.document; global.window = dom.window; }); test('createGreeting adds a greeting to the DOM', () => { // Clear the body content before the test document.body.innerHTML = ''; // Act: Call the createGreeting function const element = createGreeting('Vitest'); // Assert: Check if the greeting text is correct expect(element.textContent).toBe('Hello, Vitest!'); // Assert: Check if the correct CSS class is applied expect(element.className).toBe('greeting'); // Assert: Check if the element is actually in the document body expect(document.body.contains(element)).toBe(true); });
Этот тест проверяет корректность работы функции createGreeting, которая создает и добавляет элемент приветствия в DOM.
На данном изображении показан отчет о тестировании, связанном с DOM. Он отображает успешно пройденный тест, обозначенный зеленой галочкой. В отчете также указано общее количество выполненных тестов (1) и продолжительность теста.
Тестирование событий
// counter.js export function createCounter() { const button = document.createElement('button'); button.textContent = 'Count: 0'; let count = 0; button.addEventListener('click', () => { count++; button.textContent = `Count: ${count}`; }); return button; }
Эта функция createCounter создает кнопку, которая, когда нажата, увеличивает счетчик. Давайте рассмотрим сразу тест.
// counter.test.js import { expect, test, beforeEach } from 'vitest'; import { JSDOM } from 'jsdom'; import { createCounter } from './counter'; beforeEach(() => { const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>'); global.document = dom.window.document; global.window = dom.window; }); test('counter increments when clicked', () => { const counter = createCounter(); document.body.appendChild(counter); expect(counter.textContent).toBe('Count: 0'); counter.click(); expect(counter.textContent).toBe('Count: 1'); counter.click(); counter.click(); expect(counter.textContent).toBe('Count: 3'); });
Этот тест проверяет корректность увеличения значения компонента счетчика при клике.
Визуализация тестов с помощью графического интерфейса
Этот инструмент предоставляет вам опциональную графическую панель интерфейса пользователя (GUI) через пакет @vitest/ui. Хотя использование GUI не является обязательным, это может обогатить ваш опыт, предоставляя удобный интерфейс для управления и просмотра результатов тестирования.
npm install --save-dev @vitest/ui
Использование панели инструментов с графическим интерфейсом
После установки инструмента с флагом --ui:
npx vitest --ui
Это запустит панель инструментов GUI в вашем веб-браузере. Используя эту панель, вы сможете управлять и просматривать результаты тестирования удобным образом.
Эта панель инструментов GUI показывает результаты всех тестов, рассмотренных в этой статье. Слева вы можете увидеть список тестовых файлов, включая основные функциональные тесты, тесты API, DOM-тесты и тесты счетчика. Каждый тест отображает свой статус прохождения/провала и время выполнения. В правой панели доступна более подробная информация о выбранных тестах, включая ошибки или вывод на консоль. Этот обширный обзор позволяет разработчикам быстро оценить работоспособность всего тестового набора и при необходимости изучить конкретные результаты тестов.
Настройка повторных попыток
По умолчанию фреймворк не повторяет неудачные тесты. Однако, вы можете настроить повторы с помощью опции retry в функции test:
test('flaky test', { retry: 3 }, () => { // Your test logic here });
Вот пример нестабильного теста с параметром повтора, установленным на 3. Если тест сначала не проходит, фреймворк будет повторять его до 3 раз, прежде чем отметить его как окончательный провал.
Заключение
Эта тестовая среда, легко интегрируемая с Vite, представляет собой мощное и гибкое решение для современной веб-разработки. Она способна обрабатывать различные типы тестов: вызовы API, обработку ошибок, манипуляции DOM и тестирование событий. Разработчики используют этот инструмент сегодня, чтобы гарантировать, что их приложения всегда остаются сильными, стабильными и качественными.
Этот инструмент идеально интегрируется с быстрым рабочим процессом Vite, делая тестирование легким для начала и простым в использовании с минимальной настройкой. Для тестирования как front-end, так и back-end функционала этот фреймворк является отличным выбором, поскольку способен создавать среды браузера с помощью jsdom, что делает его идеальным решением для тестирования в современной веб-разработке. Такой подход делает модульное тестирование естественной частью процесса разработки, способствуя общему качеству и надежности ваших проектов.