vitest для TypeScript: Полное руководство
vitest — это современный, быстрый и мощный фреймворк для модульного тестирования, разработанный специально для экосистемы TypeScript и JavaScript. Он создан как прямая альтернатива Jest, но с фокусом на скорость выполнения и нативную поддержку Vite. Если вы используете Vite для сборки проекта, vitest станет идеальным выбором, так как он использует тот же плагин и конфигурацию, что и ваш основной инструмент сборки.
Зачем нужен vitest? Во-первых, он невероятно быстр благодаря использованию esbuild для транспиляции и нативной поддержке ESM (ECMAScript Modules). Во-вторых, он предлагает богатый API, совместимый с Jest, что упрощает миграцию. В-третьих, vitest предоставляет встроенную поддержку TypeScript, JSX, mocking, coverage и snapshot-тестирования без необходимости установки дополнительных пакетов. Это делает его идеальным инструментом как для небольших проектов, так и для крупных enterprise-приложений.
На этом образовательном сайте PythonLib мы покажем, как использовать vitest для тестирования TypeScript-кода. Вы узнаете, как установить библиотеку, написать первые тесты, освоить основные методы и избежать типичных ошибок. К концу статьи вы сможете уверенно использовать vitest в своих проектах.
Установка
Установка vitest осуществляется через npm или yarn. Для TypeScript-проекта вам также потребуется установить сам TypeScript и типы для Node.js (если вы тестируете серверный код).
npm install -D vitest typescript @types/node
Если вы используете yarn:
yarn add -D vitest typescript @types/node
После установки добавьте скрипт в package.json:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run"
}
}
Команда vitest запускает тесты в watch-режиме (автоматический перезапуск при изменениях), а vitest run — однократный прогон.
Быстрый старт
Создайте файл sum.ts с простой функцией:
// sum.ts
export function sum(a: number, b: number): number {
return a + b;
}
Теперь создайте файл теста sum.test.ts:
// sum.test.ts
import { describe, it, expect } from 'vitest';
import { sum } from './sum';
describe('sum function', () => {
it('should add two numbers correctly', () => {
expect(sum(2, 3)).toBe(5);
});
it('should handle negative numbers', () => {
expect(sum(-1, -2)).toBe(-3);
});
});
Запустите тесты:
npm run test
Вы увидите зеленый вывод с сообщением об успешном прохождении тестов. Поздравляем, вы написали свой первый тест на vitest!
Основные методы и функции
vitest предоставляет богатый API для написания тестов. Ниже приведены 8 ключевых методов с описанием и примерами.
1. describe
Сигнатура: describe(name: string, fn: () => void): void
Описание: Группирует связанные тесты в блок. Улучшает читаемость и позволяет применять хуки (beforeEach, afterEach) к группе.
import { describe, it, expect } from 'vitest';
describe('Math operations', () => {
it('should add', () => {
expect(1 + 1).toBe(2);
});
it('should subtract', () => {
expect(5 - 3).toBe(2);
});
});
2. it / test
Сигнатура: it(name: string, fn: () => void | Promise<void>, timeout?: number): void
Описание: Определяет отдельный тест. Можно использовать как it, так и test — это синонимы.
import { it, expect } from 'vitest';
it('should return true', () => {
expect(true).toBe(true);
});
// Асинхронный тест
it('should resolve with value', async () => {
const result = await Promise.resolve(42);
expect(result).toBe(42);
});
3. expect
Сигнатура: expect(value: any): Assertion
Описание: Основная функция для создания утверждений (assertions). Возвращает объект с методами-матчерами, такими как toBe, toEqual, toBeTruthy и другими.
import { expect } from 'vitest';
const obj = { a: 1, b: 2 };
const arr = [1, 2, 3];
expect(1 + 1).toBe(2); // строгое равенство (===)
expect(obj).toEqual({ a: 1, b: 2 }); // глубокое равенство
expect(arr).toContain(2); // проверка наличия элемента
expect(null).toBeNull(); // проверка на null
expect(undefined).toBeUndefined(); // проверка на undefined
4. beforeEach / afterEach
Сигнатура: beforeEach(fn: () => void | Promise<void>): void
Описание: Хуки, которые выполняются перед каждым тестом (или после) в текущем блоке describe. Полезны для подготовки данных или очистки.
import { describe, it, expect, beforeEach } from 'vitest';
let counter: number;
beforeEach(() => {
counter = 0;
});
describe('Counter', () => {
it('should start at 0', () => {
expect(counter).toBe(0);
});
it('should increment', () => {
counter++;
expect(counter).toBe(1);
});
});
5. vi.fn
Сигнатура: vi.fn(impl?: (...args: any[]) => any): Mock
Описание: Создает mock-функцию (заглушку). Можно задать реализацию по умолчанию или возвращаемое значение. Используется для изоляции тестируемого кода.
import { vi, it, expect } from 'vitest';
it('should call mock function', () => {
const mockFn = vi.fn((x: number) => x * 2);
const result = mockFn(5);
expect(mockFn).toHaveBeenCalledOnce();
expect(mockFn).toHaveBeenCalledWith(5);
expect(result).toBe(10);
});
6. vi.spyOn
Сигнатура: vi.spyOn(object: object, method: string): Mock
Описание: Создает mock для существующего метода объекта. Позволяет отслеживать вызовы и изменять поведение метода.
import { vi, it, expect } from 'vitest';
const calculator = {
add: (a: number, b: number) => a + b,
};
it('should spy on add method', () => {
const spy = vi.spyOn(calculator, 'add');
const result = calculator.add(2, 3);
expect(spy).toHaveBeenCalledWith(2, 3);
expect(result).toBe(5);
spy.mockRestore(); // восстановить оригинальную реализацию
});
7. vi.mock
Сигнатура: vi.mock(path: string, factory?: () => object): void
Описание: Автоматически подменяет весь модуль на mock-версию. Это глобальная операция, которая применяется ко всем тестам в файле.
// userService.ts
export const getUser = (id: number) => {
return { id, name: 'Real User' };
};
// userService.test.ts
import { vi, it, expect } from 'vitest';
vi.mock('./userService', () => ({
getUser: vi.fn(() => ({ id: 1, name: 'Mocked User' })),
}));
import { getUser } from './userService';
it('should return mocked user', () => {
const user = getUser(1);
expect(user.name).toBe('Mocked User');
});
8. vi.useFakeTimers / vi.useRealTimers
Сигнатура: vi.useFakeTimers(): void / vi.useRealTimers(): void
Описание: Позволяет управлять временем в тестах. useFakeTimers заменяет setTimeout, setInterval и Date на управляемые версии. Полезно для тестирования таймеров и асинхронных операций.
import { vi, it, expect, afterEach } from 'vitest';
afterEach(() => {
vi.useRealTimers();
});
it('should advance timers', () => {
vi.useFakeTimers();
const fn = vi.fn();
setTimeout(fn, 1000);
vi.advanceTimersByTime(1000); // перемещаем время на 1 секунду
expect(fn).toHaveBeenCalled();
});
Реальный практический пример
Рассмотрим пример тестирования небольшого сервиса для работы с задачами (TodoService). Мы протестируем CRUD-операции, используя mock для внешнего хранилища.
// todoService.ts
export interface Todo {
id: number;
title: string;
completed: boolean;
}
export interface Storage {
getTodos(): Promise;
saveTodos(todos: Todo[]): Promise;
}
export class TodoService {
private todos: Todo[] = [];
private storage: Storage;
constructor(storage: Storage) {
this.storage = storage;
}
async load(): Promise {
this.todos = await this.storage.getTodos();
}
async add(title: string): Promise {
const newTodo: Todo = {
id: Date.now(),
title,
completed: false,
};
this.todos.push(newTodo);
await this.storage.saveTodos(this.todos);
return newTodo;
}
async toggle(id: number): Promise {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
await this.storage.saveTodos(this.todos);
}
return todo;
}
async remove(id: number): Promise {
const index = this.todos.findIndex(t => t.id === id);
if (index !== -1) {
this.todos.splice(index, 1);
await this.storage.saveTodos(this.todos);
return true;
}
return false;
}
getAll(): Todo[] {
return this.todos;
}
}
Теперь напишем тесты для этого сервиса:
// todoService.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { TodoService, Storage } from './todoService';
describe('TodoService', () => {
let service: TodoService;
let mockStorage: Storage;
beforeEach(() => {
// Создаем mock для хранилища
mockStorage = {
getTodos: vi.fn().mockResolvedValue([]),
saveTodos: vi.fn().mockResolvedValue(undefined),
};
service = new TodoService(mockStorage);
});
it('should load todos from storage', async () => {
const mockTodos = [
{ id: 1, title: 'Test', completed: false },
];
mockStorage.getTodos = vi.fn().mockResolvedValue(mockTodos);
await service.load();
expect(service.getAll()).toEqual(mockTodos);
expect(mockStorage.getTodos).toHaveBeenCalledOnce();
});
it('should add a new todo', async () => {
await service.load();
const newTodo = await service.add('New Task');
expect(newTodo.title).toBe('New Task');
expect(newTodo.completed).toBe(false);
expect(service.getAll()).toHaveLength(1);
expect(mockStorage.saveTodos).toHaveBeenCalledWith(
expect.arrayContaining([expect.objectContaining({ title: 'New Task' })])
);
});
it('should toggle todo completion', async () => {
await service.load();
const added = await service.add('Task to toggle');
const toggled = await service.toggle(added.id);
expect(toggled?.completed).toBe(true);
const toggledAgain = await service.toggle(added.id);
expect(toggledAgain?.completed).toBe(false);
});
it('should remove a todo', async () => {
await service.load();
await service.add('Task to remove');
const added = service.getAll()[0];
const result = await service.remove(added.id);
expect(result).toBe(true);
expect(service.getAll()).toHaveLength(0);
expect(mockStorage.saveTodos).toHaveBeenCalledWith([]);
});
it('should return false when removing non-existent todo', async () => {
await service.load();
const result = await service.remove(999);
expect(result).toBe(false);
});
it('should handle storage errors gracefully', async () => {
mockStorage.getTodos = vi.fn().mockRejectedValue(new Error('Storage error'));
await expect(service.load()).rejects.toThrow('Storage error');
});
});
Этот пример демонстрирует использование describe, it, expect, beforeEach, vi.fn и работу с асинхронными тестами.
Советы по производительности и типичные ошибки
Советы по производительности
- Используйте
vitest runдля CI/CD. Watch-режим потребляет больше ресурсов, поэтому для однократного прогона в пайплайнах используйтеvitest run. - Избегайте глобальных моков без необходимости.
vi.mockна уровне модуля влияет на все тесты в файле. Используйтеvi.fnилиvi.spyOnдля локальных подмен. - Настройте
poolв конфигурации. Для больших проектов используйтеpool: 'forks'(вместоthreads) для изоляции тестов и
Также в библиотеке
Работа с Kafka в PHP: библиотеки, подключение и примеры кода
JWT токены в Go (Golang): библиотека golang-jwt и примеры использования
HTTP-клиент для Java: обзор топ библиотек (Java 11, Apache, OkHttp, Retrofit)
Планировщик задач на C# (.NET): Quartz.NET, Hangfire, Coravel
ORM для Rust: сравнение и примеры Diesel, SeaORM, SQLx