При разработке проектов на базе Vite часто возникает необходимость использовать существующую инфраструктуру тестирования, построенную вокруг Jest. Особенно это актуально при:
Несмотря на активное развитие Vitest, Jest остаётся одной из самых распространённых тестовых платформ в экосистеме JavaScript. Поэтому Vite предоставляет различные механизмы совместимости и интеграции.
Vite использует:
Jest исторически ориентирован на:
Из-за этого между Vite и Jest возникают следующие несовместимости:
| Область | Vite | Jest |
|---|---|---|
| Модули | ESM | CJS/частично ESM |
| Импорт CSS | Поддерживается | Требует mock |
| import.meta.env | Есть | Нет |
| Алиасы Vite | Есть | Нужно дублировать |
| HMR API | Есть | Нет |
| URL imports | Есть | Частично |
| Asset imports | Есть | Требует настройки |
npm install -D jest
Для TypeScript:
npm install -D typescript ts-jest @types/jest
Для Babel:
npm install -D babel-jest @babel/core @babel/preset-env
project/
├── src/
├── tests/
├── vite.config.js
├── jest.config.js
└── package.json
// jest.config.js
export default {
testEnvironment: 'jsdom'
};
Однако этого недостаточно для полноценной совместимости.
Vite использует ESM по умолчанию:
import { sum } from './math.js';
Jest долгое время работал исключительно через CommonJS:
const { sum } = require('./math');
Современные версии Jest поддерживают ESM, но требуют дополнительной настройки.
{
"type": "module"
}
// jest.config.js
export default {
testEnvironment: 'jsdom',
extensionsToTreatAsEsm: ['.js']
};
Иногда требуется запуск:
node --experimental-vm-modules node_modules/jest/bin/jest.js
В package.json:
{
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
}
}
Vite внедряет переменные окружения через:
import.meta.env
Пример:
const apiUrl = import.meta.env.VITE_API_URL;
Jest не понимает import.meta.
SyntaxError: Cannot use 'import.meta' outside a module
// tests/setup-env.js
global.import = {
meta: {
env: {
VITE_API_URL: 'http://localhost:3000'
}
}
};
// jest.config.js
export default {
setupFiles: ['./tests/setup-env.js']
};
Лучше изолировать доступ к окружению:
// env.js
export const API_URL = import.meta.env.VITE_API_URL;
Тогда в тестах можно мокать модуль:
jest.mock('./env.js', () => ({
API_URL: 'mock-url'
}));
// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
}
});
Jest не знает о конфигурации Vite.
// jest.config.js
export default {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
}
};
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'^components/(.*)$': '<rootDir>/src/components/$1',
'^utils/(.*)$': '<rootDir>/src/utils/$1'
}
Vite позволяет:
import './style.css';
Jest не умеет обрабатывать CSS без mock-механизма.
Unexpected token '.'
npm install -D identity-obj-proxy
// jest.config.js
export default {
moduleNameMapper: {
'\\.(css|scss|sass)$': 'identity-obj-proxy'
}
};
import styles from './Button.module.css';
export function Button() {
return <button className={styles.btn}>OK</button>;
}
expect(styles.btn).toBe('btn');
import logo from './logo.png';
Jest не умеет импортировать asset-файлы напрямую.
export default 'test-file-stub';
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/tests/mocks/fileMock.js'
}
Babel помогает Jest:
npm install -D babel-jest @babel/core @babel/preset-env
Для React:
npm install -D @babel/preset-react
export default {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
};
export default {
transform: {
'^.+\\.jsx?$': 'babel-jest'
}
};
npm install -D ts-jest @types/jest
// jest.config.js
export default {
preset: 'ts-jest',
testEnvironment: 'jsdom'
};
transform: {
'^.+\\.tsx?$': 'ts-jest'
}
npm install -D @testing-library/react
export default {
testEnvironment: 'jsdom'
};
import { render, screen } from '@testing-library/react';
import { Button } from './Button';
test('renders button', () => {
render(<Button />);
expect(screen.getByText('OK')).toBeInTheDocument();
});
npm install -D @testing-library/jest-dom
import '@testing-library/jest-dom';
export default {
setupFilesAfterEnv: ['./tests/setupTests.js']
};
npm install -D @vue/vue3-jest
transform: {
'^.+\\.vue$': '@vue/vue3-jest'
}
Современные версии vue-jest поддерживают:
<script setup>
</script>
Однако возможны проблемы совместимости версий Vue, Jest и Babel.
Vite активно использует:
npm install -D babel-plugin-dynamic-import-node
export default {
plugins: [
'dynamic-import-node'
]
};
const modules = import.meta.glob('./modules/*.js');
Jest не поддерживает этот API.
// loader.js
export function loadModules() {
return import.meta.glob('./modules/*.js');
}
jest.mock('./loader.js', () => ({
loadModules: () => ({
'./a.js': jest.fn(),
'./b.js': jest.fn()
})
}));
const worker = new Worker(
new URL('./worker.js', import.meta.url)
);
Jest не поддерживает Worker API из коробки.
class WorkerMock {
postMessage() {}
terminate() {}
}
global.Worker = WorkerMock;
import Logo from './logo.svg?react';
Jest не понимает query-параметры.
moduleNameMapper: {
'\\.svg\\?react$': '<rootDir>/tests/mocks/svgComponentMock.js'
}
export default function SvgMock() {
return null;
}
Некоторые npm-пакеты публикуются только как ESM.
Jest по умолчанию не трансформирует node_modules.
Unexpected token export
export default {
transformIgnorePatterns: [
'/node_modules/(?!(package-name)/)'
]
};
// jest.config.js
export default {
testEnvironment: 'jsdom',
setupFilesAfterEnv: [
'<rootDir>/tests/setupTests.js'
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|scss|sass)$':
'identity-obj-proxy',
'\\.(png|jpg|jpeg|gif|svg)$':
'<rootDir>/tests/mocks/fileMock.js'
},
transform: {
'^.+\\.[jt]sx?$': 'babel-jest'
},
transformIgnorePatterns: [
'/node_modules/(?!(some-esm-package)/)'
]
};
Возможна совместная работа:
{
"scripts": {
"test": "vitest",
"test:jest": "jest"
}
}
Vitest:
Vitest специально повторяет API Jest:
describe()
test()
expect()
beforeEach()
afterEach()
vi.fn()
jest.fn();
vi.fn();
Многие тесты можно перенести почти без изменений:
import { describe, test, expect } from 'vitest';
Несмотря на большое количество настроек, Jest и Vite остаются разными системами.
Проблемные области:
При увеличении проекта конфигурация Jest может стать значительно сложнее:
Хорошей практикой считается изоляция:
// platform/env.js
export function getApiUrl() {
return import.meta.env.VITE_API_URL;
}
jest.mock('./platform/env.js', () => ({
getApiUrl: () => 'mock-api'
}));
Jest остаётся актуальным при:
Vitest обычно предпочтительнее при: