🏆 151 курс за 1 подписку: хватит выбирать — бери все и сразу!

Один клик — 151 возможность. Подпишись на OTUS сейчас!
Техномир мчится вперед, а вместе с ними растут и требования к специалистам. OTUS придумал крутую штуку — подписку на 151 курс по всем ключевым направлениям IT!
-
Почему подписка OTUS меняет правила игры:
- Доступ к 151 курсу от практикующих экспертов
- В 3 раза выгоднее, чем покупать каждый курс отдельно
- До 3 курсов одновременно без дополнительных затрат
- Свобода выбора направления — меняй треки когда угодно
Изучай новое, развивайся в своем темпе, меняй направления — подпишись на OTUS и прокачивай скилы по полной!
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576. Erid 2VtzqupFnNL
Привет, друзья! Я Кирилл Мыльников, 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, что делает его идеальным решением для тестирования в современной веб-разработке. Такой подход делает модульное тестирование естественной частью процесса разработки, способствуя общему качеству и надежности ваших проектов.
Комментарии