Кирилл Мыльников 23 сентября 2024

🔍🏃 С Vitest ваше тестирование в Vite станет легким и эффективным

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
    
  1. Создайте новый проект с названием my-vite-project и настройками по умолчанию.
  2. Установите фреймворк: добавьте тест-раннер в качестве зависимости разработки, выполнив команду 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 и выведет результаты. В случае успешного прохождения утверждения будет отображено сообщение об успехе. В случае неудачи вы увидите сообщение об ошибке, которое указывает на проблему.

🔍🏃 С Vitest ваше тестирование в Vite станет легким и эффективным

Преимущество этого фреймворка заключается в его нативной интеграции с существующим конвейером сборки 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. Давайте его рассмотрим:

  1. В наших тестах мы имитируем функцию fetch, чтобы контролировать ее поведение.
  2. Мы настраиваем фиктивный объект пользователя, который должен возвращаться при вызове API.
  3. Мы используем 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:

  1. Мы создаем фиктивный объект нового пользователя и фиктивный ответ.
  2. Мы моделируем успешный ответ от API при создании пользователя.
  3. Затем мы вызываем userAPI.createUser(newUser) и проверяем, что созданный пользователь соответствует нашему фиктивному ответу.
  4. Также мы проверяем, что функция 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:

  1. Устанавливает тестовые данные: идентификатор пользователя и обновленную информацию о нем.
  2. Создает фиктивный ответ, который обычно возвращает API.
  3. Настраивает функцию фиктивной выборки для возврата успешного ответа с фиктивными данными.
  4. Вызывает функцию updateUser с тестовыми данными.
  5. Проверяет, что данные, возвращаемые функцией, соответствуют ожидаемому фиктивному ответу.
  6. Проверяет, что функция 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:

  1. Создает тестовый идентификатор пользователя.
  2. Настраивает функцию фиктивной выборки для возврата успешного ответа.
  3. Вызывает функцию deleteUser с тестовым идентификатором пользователя.
  4. Проверяет, что функция возвращает значение true, указывающее на успешное удаление.
  5. Наконец, проверяет, была ли вызвана функция 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 на ошибки и проверяют, что каждая функция возвращает ошибку с ожидаемым сообщением, если запрос был неуспешным.

🔍🏃 С Vitest ваше тестирование в Vite станет легким и эффективным

На изображении представлен отчет о тестировании, связанном с 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.

🔍🏃 С Vitest ваше тестирование в Vite станет легким и эффективным

На данном изображении показан отчет о тестировании, связанном с 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');
});
    

Этот тест проверяет корректность увеличения значения компонента счетчика при клике.

🔍🏃 С Vitest ваше тестирование в Vite станет легким и эффективным

Визуализация тестов с помощью графического интерфейса

Этот инструмент предоставляет вам опциональную графическую панель интерфейса пользователя (GUI) через пакет @vitest/ui. Хотя использование GUI не является обязательным, это может обогатить ваш опыт, предоставляя удобный интерфейс для управления и просмотра результатов тестирования.

        npm install --save-dev @vitest/ui
    

Использование панели инструментов с графическим интерфейсом

После установки инструмента с флагом --ui:

        npx vitest --ui
    

Это запустит панель инструментов GUI в вашем веб-браузере. Используя эту панель, вы сможете управлять и просматривать результаты тестирования удобным образом.

🔍🏃 С Vitest ваше тестирование в Vite станет легким и эффективным

Эта панель инструментов 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, что делает его идеальным решением для тестирования в современной веб-разработке. Такой подход делает модульное тестирование естественной частью процесса разработки, способствуя общему качеству и надежности ваших проектов.

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Senior Java Developer
Москва, по итогам собеседования
Разработчик С#
от 200000 RUB до 400000 RUB
Java Team Lead
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ