Интеграционные тесты проверяют взаимодействие нескольких модулей, подсистем и инфраструктурных компонентов одновременно. В контексте Webpack это особенно важно, поскольку итоговое приложение формируется не напрямую из исходного кода, а через сложный этап трансформации:
Даже если отдельные модули успешно проходят unit-тестирование, ошибки могут появляться именно после сборки. Интеграционные тесты позволяют обнаруживать проблемы, связанные с:
Webpack-сборка в интеграционных тестах становится полноценным объектом тестирования.
Типичная архитектура интеграционного тестирования включает несколько уровней:
Проверяется:
После сборки:
Проверяется:
Интеграционные тесты редко используют production-конфигурацию напрямую. Обычно создаётся отдельный webpack-конфиг.
project/
├─ src/
├─ tests/
│ ├─ integration/
│ ├─ fixtures/
│ └─ webpack/
├─ webpack.config.js
├─ webpack.test.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: path.resolve(__dirname, 'tests/fixtures/app.js'),
output: {
path: path.resolve(__dirname, 'tests/dist'),
filename: 'bundle.js',
clean: true
}
};
Такой конфиг:
Webpack предоставляет программный API для запуска сборки напрямую из тестов.
npm install webpack jest --save-dev
const webpack = require('webpack');
const config = require('../. ./webpack.test.config');
describe('Webpack build', () => {
test('should compile successfully', done => {
webpack(config, (err, stats) => {
expect(err).toBeNull();
const info = stats.toJson();
expect(stats.hasErrors()).toBe(false);
expect(info.errors.length).toBe(0);
done();
});
});
});
Иногда warning считается ошибкой сборки.
expect(stats.hasWarnings()).toBe(false);
Либо анализируется конкретный warning:
const warnings = stats.toJson().warnings;
expect(warnings).not.toContainEqual(
expect.stringContaining('deprecated')
);
Webpack позволяет получить список файлов сборки.
const assets = stats.toJson().assets;
expect(
assets.some(asset => asset.name === 'bundle.js')
).toBe(true);
Интеграционные тесты часто проверяют code splitting.
optimization: {
splitChunks: {
chunks: 'all'
}
}
const chunks = stats.toJson().chunks;
expect(chunks.length).toBeGreaterThan(1);
button.addEventListener('click', async () => {
const module = await import('./dialog');
module.openDialog();
});
Проверяется:
const assets = stats.toJson().assets;
const hasAsyncChunk = assets.some(asset =>
asset.name.includes('dialog')
);
expect(hasAsyncChunk).toBe(true);
Tree shaking — одна из наиболее критичных оптимизаций Webpack.
export function used() {
return 1;
}
export function unused() {
console.log('remove me');
}
import { used } from './math';
used();
mode: 'production',
optimization: {
usedExports: true
}
const fs = require('fs');
const bundle = fs.readFileSync(
'./tests/dist/bundle.js',
'utf8'
);
expect(bundle.includes('remove me')).toBe(false);
Webpack учитывает поле sideEffects.
{
"sideEffects": false
}
Интеграционные тесты позволяют убедиться, что:
Webpack и Babel тесно интегрированы через
babel-loader.
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
}
]
}
class User {
#name = 'Alex';
getName() {
return this.#name;
}
}
const bundle = fs.readFileSync(
'./tests/dist/bundle.js',
'utf8'
);
expect(bundle.includes('#name')).toBe(false);
{
test: /\.ts$/,
use: 'ts-loader'
}
expect(stats.compilation.errors.length).toBe(0);
Source maps критичны для:
devtool: 'source-map'
const assets = stats.toJson().assets;
expect(
assets.some(asset => asset.name.endsWith('.map'))
).toBe(true);
Иногда bundle сравнивается через snapshots.
expect(bundle).toMatchSnapshot();
Однако snapshot bundle имеет недостатки:
Поэтому snapshot-тестирование применяется ограниченно.
Размер сборки часто контролируется интеграционными тестами.
const statsData = stats.toJson({
assets: true
});
const bundleAsset = statsData.assets.find(
asset => asset.name === 'bundle.js'
);
expect(bundleAsset.size).toBeLessThan(300000);
Webpack может включать дублирующиеся зависимости.
Интеграционные тесты помогают обнаружить:
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
const bundle = fs.readFileSync(
'./tests/dist/bundle.js',
'utf8'
);
expect(bundle.includes('background')).toBe(true);
{
test: /\.module.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
expect(bundle.includes('_button_')).toBe(true);
Webpack 5 заменил:
{
test: /\.(png|jpg)$/i,
type: 'asset/resource'
}
const assets = stats.toJson().assets;
const imageAsset = assets.find(asset =>
asset.name.endsWith('.png')
);
expect(imageAsset).toBeDefined();
{
test: /\.svg$/,
type: 'asset/inline'
}
expect(bundle.includes('data:image/svg+xml')).toBe(true);
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
expect(stats.hasErrors()).toBe(false);
Ошибки alias обычно проявляются как:
Module not found
externals: {
react: 'React'
}
expect(bundle.includes('React')).toBe(true);
expect(bundle.includes('node_modules/react')).toBe(false);
Тестирование webpack-dev-server значительно сложнее
обычной сборки.
Проверяются:
const WebpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');
const compiler = webpack(config);
const server = new WebpackDevServer(
{
port: 9000
},
compiler
);
Интеграционные тесты HMR обычно используют:
Проверяется:
npm install playwright --save-dev
const { test, expect } = require('@playwright/test');
test('application should work after webpack build', async ({
page
}) => {
await page.goto('http://localhost:9000');
await expect(page.locator('h1'))
.toHaveText('Application');
});
Production-сборка имеет особенности:
Интеграционные тесты должны запускаться отдельно для production mode.
mode: 'production'
output: {
filename: '[name].[contenthash].js'
}
const assets = stats.toJson().assets;
expect(
assets.some(asset =>
/\.[a-f0-9]{20}\.js$/.test(asset.name)
)
).toBe(true);
Webpack 5 использует deterministic ids.
optimization: {
moduleIds: 'deterministic'
}
Интеграционные тесты помогают контролировать стабильность кэша.
Module Federation требует отдельного уровня интеграционных тестов.
Проверяется:
expect(
assets.some(asset =>
asset.name === 'remoteEntry.js'
)
).toBe(true);
SSR-конфигурации Webpack отличаются:
target: 'node'
const serverBundle = require(
'../. ./tests/dist/server.js'
);
expect(serverBundle.render).toBeDefined();
Plugins изменяют внутренний pipeline Webpack.
Проверяются:
class BannerPlugin {
apply(compiler) {
compiler.hooks.emit.tap(
'BannerPlugin',
compilation => {
Object.keys(compilation.assets).forEach(
filename => {
const source =
compilation.assets[filename].source();
compilation.assets[filename] = {
source: () => '/* banner */\n' + source,
size: () => source.length
};
}
);
}
);
}
}
expect(bundle.startsWith('/* banner */'))
.toBe(true);
Watch mode важен для DX и CI-пайплайнов.
const watching = compiler.watch({}, () => {
console.log('rebuild');
});
Тесты обычно:
Интеграционные тесты Webpack активно используют временные директории.
const fs = require('fs');
afterAll(() => {
fs.rmSync('./tests/dist', {
recursive: true,
force: true
});
});
Для ускорения тестов применяется in-memory filesystem.
npm install memfs --save-dev
const { Volume } = require('memfs');
const vol = new Volume();
compiler.outputFileSystem = vol;
Преимущества:
unionfs позволяет комбинировать:
Это полезно при:
Интеграционные тесты Webpack часто запускаются параллельно.
Проблемы:
Решение — генерация уникальных output-директорий.
const os = require('os');
const path = require('path');
const outputPath = path.join(
os.tmpdir(),
`webpack-test-${Date.now()}`
);
Webpack 5 поддерживает persistent cache.
cache: {
type: 'filesystem'
}
Интеграционные тесты могут сравнивать:
Webpack предоставляет подробную статистику.
const statsData = stats.toJson({
all: false,
assets: true,
chunks: true,
modules: true
});
Через stats можно анализировать:
Webpack может отключать оптимизации.
const modules = stats.toJson({
optimizationBailout: true
}).modules;
Такие тесты помогают находить:
Webpack-интеграционные тесты часто выполняются:
Особенно важно тестировать:
Ошибка:
expect(stats.hasErrors()).toBe(false);
Недостаток — runtime может быть сломан.
Минификация делает snapshots нестабильными.
Параллельные тесты начинают конфликтовать.
Многие warning Webpack сигнализируют о серьёзных проблемах:
Даже корректно собранный bundle может падать в браузере.
Наиболее стабильный подход включает:
Такая стратегия позволяет тестировать не только исходный код приложения, но и всю инфраструктуру Webpack-сборки целиком.