При тестировании приложений на JavaScript и TypeScript часто возникает проблема несовместимости исходного кода с окружением выполнения тестов. Код может содержать:
Webpack обычно решает эти задачи на этапе сборки, однако Jest запускает файлы напрямую внутри Node.js и не использует Webpack автоматически. Из-за этого тестовая среда не понимает современный синтаксис без дополнительной трансформации.
Для решения этой проблемы используются:
babel-jest;ts-jest;Webpack выполняет:
Jest не занимается сборкой проекта. Его задача — быстро запускать тесты и изолированно исполнять модули.
При запуске теста Jest:
Webpack в этой цепочке отсутствует.
Из-за этого код вроде:
import Button from '@/components/Button';
const element = <Button />;
не будет работать без дополнительных преобразований.
Jest использует систему transform.
Пример:
module.exports = {
transform: {
'^.+\\.jsx?$': 'babel-jest'
}
};
Алгоритм работы:
babel-jest — официальный трансформер Jest для Babel.
Он:
npm install --save-dev jest babel-jest @babel/core
Для React:
npm install --save-dev @babel/preset-env @babel/preset-react
Для TypeScript через Babel:
npm install --save-dev @babel/preset-typescript
module.exports = {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
};
Для TypeScript:
module.exports = {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
]
};
module.exports = {
transform: {
'^.+\\.[jt]sx?$': 'babel-jest'
}
};
Регулярное выражение:
^.+\\.[jt]sx?$
поддерживает:
.js.jsx.ts.tsxИсходный файл:
const App = () => {
return <div>Hello</div>;
};
После Babel:
const App = () => {
return React.createElement("div", null, "Hello");
};
Node.js уже способен выполнить такой код.
Ключевая особенность Babel:
Пример:
const value: string = 100;
Babel преобразует это в:
const value = 100;
Ошибки типов обнаружены не будут.
При использовании Babel обычно добавляют отдельную проверку:
tsc --noEmit
Часто это выполняется:
Пример:
{
"scripts": {
"typecheck": "tsc --noEmit"
}
}
ts-jest — специализированный трансформер Jest для
TypeScript.
Он:
npm install --save-dev jest ts-jest typescript
npx ts-jest config:init
Автоматически создаётся конфигурация:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node'
};
ts-jest использует TypeScript compiler вместо Babel.
Алгоритм:
{
"compilerOptions": {
"target": "ES2019",
"module": "commonjs",
"jsx": "react-jsx",
"strict": true
}
}
В отличие от Babel, ts-jest умеет диагностировать ошибки
типов.
Пример:
const value: string = 100;
Во время тестов может появиться ошибка TypeScript.
Для ускорения компиляции часто используется:
globals: {
'ts-jest': {
isolatedModules: true
}
}
В этом режиме:
| Возможность | babel-jest | ts-jest |
|---|---|---|
| Поддержка Babel plugins | Да | Частично |
| Проверка типов | Нет | Да |
| Скорость | Выше | Ниже |
| JSX | Да | Да |
| Experimental syntax | Отлично | Ограниченно |
| Совместимость с React ecosystem | Отлично | Хорошо |
| Использование tsconfig | Частично | Полностью |
| Работа с decorators | Через Babel plugins | Через TypeScript |
babel-jest предпочтителен, если:
tsc.ts-jest подходит, если:
Иногда используются обе технологии одновременно.
Схема:
TypeScript
↓
ts-jest
↓
Babel
↓
Jest runtime
Либо:
TypeScript
↓
Babel preset-typescript
↓
Jest
@babel/preset-typescript удаляет типы, но:
presets: [
'@babel/preset-react'
]
Поддержка зависит от:
{
"jsx": "react-jsx"
}
Современные проекты всё чаще используют:
import value from './module.js';
Node.js и Jest долгое время ориентировались на CommonJS:
const value = require('./module');
Без настройки возникают ошибки:
Cannot use import statement outside a module
или:
Unexpected token export
Babel может преобразовать ESM в CommonJS:
presets: [
[
'@babel/preset-env',
{
modules: 'commonjs'
}
]
]
Для ESM требуется:
module.exports = {
preset: 'ts-jest/presets/default-esm'
};
Также часто используется:
{
"module": "ESNext"
}
Webpack использует:
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
Jest этого не понимает.
Необходима отдельная настройка:
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
}
Webpack:
Jest:
По умолчанию Jest игнорирует:
node_modules
Причина — производительность.
Иногда пакеты публикуются в неподдерживаемом синтаксисе:
Unexpected token export
Тогда используется:
transformIgnorePatterns: [
'/node_modules/(?!(my-esm-lib)/)'
]
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.[jt]sx?$': 'babel-jest'
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
transformIgnorePatterns: [
'/node_modules/(?!(some-esm-package)/)'
]
};
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current'
}
}
],
'@babel/preset-react',
'@babel/preset-typescript'
]
};
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
}
};
TypeScript поддерживает:
{
"paths": {
"@/*": ["src/*"]
}
}
Jest автоматически это не читает.
ts-jest предоставляет helper:
const { pathsToModuleNameMapper } = require('ts-jest');
const { compilerOptions } = require('./tsconfig');
module.exports = {
moduleNameMapper: pathsToModuleNameMapper(
compilerOptions.paths,
{ prefix: '<rootDir>/' }
)
};
Для корректных stack traces используются source maps.
Babel:
sourceMaps: 'inline'
TypeScript:
{
"sourceMap": true
}
Преимущества:
Недостаток:
Преимущества:
Недостатки:
Jest кеширует результат компиляции.
Кеш зависит от:
Очистка:
jest --clearCache
Обычно означает:
Причины:
Отсутствует:
@babel/preset-react
JSX не был преобразован.
Можно использовать любые Babel plugins:
plugins: [
'@babel/plugin-proposal-decorators'
]
Это особенно важно для:
Webpack поддерживает:
Jest не умеет это выполнять напрямую.
Для CSS:
moduleNameMapper: {
'\\.(css|scss)$': 'identity-obj-proxy'
}
Для изображений:
moduleNameMapper: {
'\\.(png|jpg|svg)$': '<rootDir>/__mocks__/fileMock.js'
}
module.exports = 'test-file-stub';
React Testing Library почти всегда используется вместе с Babel.
Причины:
В monorepo часто возникают проблемы:
Для monorepo используется:
module.exports = {
projects: [
'<rootDir>/packages/app',
'<rootDir>/packages/core'
]
};
В CI кеш иногда становится причиной нестабильности.
Отключение:
jest --no-cache
Распространённая современная схема:
Причины популярности:
Современные проекты иногда заменяют Babel на:
Для Jest существуют:
@swc/jest;esbuild-jest.Они работают быстрее, однако:
Webpack/Vite → production build
Jest
↓
babel-jest
↓
Babel
↓
Node.js runtime
TypeScript
↓
tsc --noEmit
Такая схема разделяет:
Это уменьшает связанность инструментов и ускоряет инфраструктуру проекта.