如何在Jest快照测试中支持文件大小±5%的非精确匹配
问题描述
我正在为一款构建工具编写测试套件,目前手动维护如下示例的快照耗费大量时间:
export const defaultBuild = { 'bundle.abcde.js': 1000, 'bundle.abcde.css': 112, }
我们需要捕获文件哈希变更以及超出±5%范围的文件大小变更,但每次变更都需手动改写快照,实际有数十条条目,十分耗时。理想方案是切换为Jest快照测试,利用其可通过标记自动更新的特性提升效率。
目前我已实现稳定文件哈希的测试,通过readdir处理可得到字符串化的目录结构,但在处理文件大小时遇到困难:必须允许±5%的容差,否则快照会频繁更新过于嘈杂,但Jest快照测试仅支持精确匹配,无自定义比较器。
请问是否有合适的方法,可在快照中存储文件大小并支持自动更新,同时允许一定的容差范围?
以下是我的初步尝试代码:
expect.extend({ // Pending more thoughtful name toSortaMatchSnapshot(dir) { const lines = dir.split('\n'); for (const line of lines) { const match = line.match(/(^[^:]*):\s([0-9]*)/); if (match) { let fileName = match[1].trim(); if (/\.\w{5}/.test(fileName)) { fileName = fileName.replace(/\.\w{5}/, ''); } const fileSize = Number(match[2]); const expectedMin = fileSize * 0.95; const expectedMax = fileSize * 1.05; //const message = (comparator, val) => // `expected '${fileName}' to be ${comparator} than ${val}, but it's ${receivedSize}`; // This won't really ever work toMatchSnapshot.call(this, fileSize, fileName); } } return { message: () => '', pass: true, }; }, });
示例输入如下:
`build bundle.abcde.js: 1000 bundle.abcde.css: 112 `;
解决方案
方法一:自定义快照序列化器
Jest支持自定义快照序列化逻辑,你可以将文件大小数据标准化(去掉哈希后缀)后再生成快照,配合后续的容差对比逻辑使用。
步骤1:编写序列化器
创建__snapshots__/fileSizeSerializer.js文件:
module.exports = { test(val) { // 识别需要处理的构建文件大小对象 return typeof val === 'object' && val !== null && Object.keys(val).every(key => /bundle\.\w{5}\.(js|css)/.test(key)); }, print(val) { // 去掉文件名中的5位哈希,保留基准大小 const normalized = {}; for (const [key, size] of Object.entries(val)) { normalized[key.replace(/\.\w{5}/, '')] = size; } return JSON.stringify(normalized, null, 2); } };
步骤2:配置Jest加载序列化器
在jest.config.js中添加:
module.exports = { snapshotSerializers: ['<rootDir>/__snapshots__/fileSizeSerializer.js'], };
方法二:自定义容差匹配器(推荐)
扩展Jest的expect匹配器,实现容差对比+自动更新快照的逻辑,比序列化器更灵活,能提供详细的错误提示。
编写自定义匹配器
expect.extend({ toMatchSizeSnapshot(received, { tolerance = 0.05 } = {}) { const snapshotPath = this.utils.getSnapshotPath(); let baseline = {}; // 读取现有快照(如果存在) try { baseline = require(snapshotPath).default; } catch {} // 标准化收到的文件数据:去掉哈希后缀 const normalizedReceived = {}; for (const [fileName, size] of Object.entries(received)) { normalizedReceived[fileName.replace(/\.\w{5}/, '')] = size; } const errors = []; let pass = true; // 检查所有文件的大小是否在容差范围内 for (const [key, currentSize] of Object.entries(normalizedReceived)) { if (baseline[key]) { const min = baseline[key] * (1 - tolerance); const max = baseline[key] * (1 + tolerance); if (currentSize < min || currentSize > max) { pass = false; errors.push(`文件 ${key} 超出容差:预期 ${baseline[key]} ±${tolerance*100}%,实际 ${currentSize}`); } } else { pass = false; errors.push(`发现新文件 ${key},需更新快照`); } } // 检查快照中已删除的文件 for (const key of Object.keys(baseline)) { if (!normalizedReceived[key]) { pass = false; errors.push(`文件 ${key} 已删除,需更新快照`); } } // 处理快照更新逻辑 if (!pass && this.utils.shouldUpdateSnapshot()) { const fs = require('fs'); const content = `export default ${JSON.stringify(normalizedReceived, null, 2)};`; fs.writeFileSync(snapshotPath, content, 'utf8'); pass = true; // 更新后标记为通过 } return { pass, message: () => errors.join('\n'), }; }, });
在测试中使用匹配器
const fs = require('fs'); const path = require('path'); test('构建文件大小符合±5%容差要求', () => { const buildDir = path.join(__dirname, 'build'); const files = fs.readdirSync(buildDir); const fileSizes = {}; for (const file of files) { const stats = fs.statSync(path.join(buildDir, file)); fileSizes[file] = stats.size; } expect(fileSizes).toMatchSizeSnapshot({ tolerance: 0.05 }); });
使用说明
- 当文件大小超出±5%、新增/删除文件时,测试会报错,此时运行
jest -u即可自动更新快照 - 确保文件名的哈希部分是固定长度(比如5位),正则替换才能准确生效
- 自定义匹配器会自动处理快照的读取和写入,无需手动修改快照文件
内容的提问来源于stack exchange,提问作者rschristian
相关产品推荐
相关产品推荐

