如何自定义 jest 的快照序列化

857 阅读3分钟

今天想要给项目的 webpack 配置做个存档,所以首选了 Snapshot Testing 对最终生成的配置进行记录。

此时遇到了一个问题:配置文件中会出现一些绝对路径字符串,例如:"entry": "/local-path/my-app/src/index" 。这里的 local-path 就是我本地电脑的项目路径,很明显,这个路径在不同的电脑上可能会不同,这样这个单元测试快照就起不到基准的作用了。

为了解决这个问题,我需要将这个环境有关的信息给固定下来。

我首先想到的解决方案是 mock 掉这个字符串产生的源头。

mock path 模块

我发现这样的字符串产生的源头是如下代码:

const path = require('path');
path.resolve(appDirectory, relativePath)

既然这样,那我将 path 模块 mock 一下不就可以了?说干就干。。。

jest.mock('path', () => ({
  ...jest.requireActual('path'),
  resolve: () => '/app-directory', // mock resolve 方法,返回一个固定的字符串
}));

运行一下,结果发现报错了:

Cannot find module '/app-directory' from 'config/paths.js'

为啥会这样报错呢?

原因是我项目中的 webpack 配置文件并不是一个单一的文件,是多个文件最终合并成一份配置,mock 了 path 模块之后,文件之间的导入导出被破坏了,导致配置压根无法生成。

所以并不能使用 mock 路径依赖模块的方法来解决这个问题。

使用 expect.addSnapshotSerializer(serializer) API

既然导入的源头无法解决问题,那么能不能在输出的地方做替换呢?也就是在生成快照的时候将数据替换掉。

经过一通查找文档,最终还真让我找到了对应的 api: expect.addSnapshotSerializer(serializer)

配置示例 snapshotSerializers` [array]

// my-serializer-module

module.exports = {

serialize(val, config, indentation, depth, refs, printer) {

return 'Pretty foo: ' + printer(val.foo);

},

test(val) {

return val && val.hasOwnProperty('foo');

},

};

然后我先直接 copy 到本地看看配置是否有用,接着就报错了,摔。。。

    PrettyFormatPluginError: Cannot read property 'plugins' of undefinedTypeError: Cannot read property 'plugins' of undefined

      12 |     const customSerializer = {
      13 |       serialize(val, config, indentation, depth, refs, printer) {
    > 14 |         return 'Pretty foo: ' + printer(val.foo);
         |                                 ^
      15 |       },
      

既然官网示例离奇不顶用,那就继续查找资料吧,然后就发现这个 Creating a Custom Jest Snapshot Serialize。这篇博文的例子和我的场景很搭配,示例如下:

// 这是上面那篇博文的例子,如果是 react 组件的 id 都给替换成 'ABC123'
expect.addSnapshotSerializer({
  test: val => typeof val === 'string',
  print: val => {
    console.log(`val="${val}"`);
    const newVal = val.replace(/^[A-Z0-9]{10}$/g, 'ABC123');
    console.log(`newVal="${newVal}"`);
    return `"${newVal}"`;
  },
});

那我就直接照着写一个:

    // 将配置中的本地路径替换掉,防止换个电脑就测试通不过
    const customSerializer = {
      serialize(val, config, indentation, depth, refs, printer) {
        return (val as string).replace(appDirectory, '/app-directory');
      },

      test(val) {
        return typeof val === 'string' && (val as string)?.includes?.(appDirectory);
      },
    };
    expect.addSnapshotSerializer(customSerializer as any);

最终效果:

  "entry": Array [
    /app-directory/config/polyfills.js,
    /app-directory/node_modules/react-dev-utils/webpackHotDevClient.js,
    /app-directory/src/index.tsx,
  ],

完美~~

结语

本文讲解了如何自定义一个 jest 的序列化方法,如果你有相同的需求可以参考实现。
谢谢大家的观看。