You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 05:05:36