lmk123 / blog

个人技术博客,博文写在 Issues 里。
https://github.com/lmk123/blog/issues
623 stars 35 forks source link

Jest 如何在 import 模块前运行代码 #116

Open lmk123 opened 1 year ago

lmk123 commented 1 year ago

在写测试时,我经常需要提前准备好测试代码,比如如果我想测试下面这个模块:

// testMe.ts

import otherModule from 'other-module'

// 读取其它模块的值
const id = otherModule.id

// 读取环境变量
const key = process.env.KEY

// 读取全局变量
const ua = navigator.userAgent

export function testMe() {
  return id + ':' + key + ':' + ua
}

假设我的测试用例是这么写的:

// testMe.test.ts

import { testMe } from './testMe'

test('s', () => {
  expect(testMe()).toBe('a:b:c')
})

如果就这样运行,那么 testMe() 的结果会是 undefined:undefined:undefined,怎样才能确保这个值变成我想要的 a:b:c

对于模块,可以用 jest.mock

jest 会将 jest.mock 提升至所有模块 import 之前运行:

// testMe.test.ts

import { testMe } from './testMe'

jest.mock('other-module', () => ({ id: 'a' })) // 即使它写在 import testMe 之后,实际上它会在 import 前运行

test('s', () => {
  expect(testMe()).toBe('a:b:c')
})

对于环境变量 / 全局变量,可以用 setupFiles

利用 setupFiles 提前准备好即可:

// jest-setup.js
process.env.KEY = 'b'
global.navigator = { userAgent: 'c' }

直接把代码写在 import 前是不行的。

// 虽然这两行代码写在 import testMe 前面,但实际上 testMe.ts 里的代码会先运行,因为 babel 会将 import 全提升到顶部。
// `jest.mock()` 之所以能提升到 import 前面,是因为 jest 自己开发了一个 babel 插件
// https://www.npmjs.com/package/babel-plugin-jest-hoist
process.env.KEY = 'b'
global.navigator = { userAgent: 'c' }

import { testMe } from './testMe'

但 setupFiles 对所有测试文件都会效果。如果只想让它对部分测试文件生效,就要在 jest.config.js 里用到 projects:

// jest.config.js
module.exports = {
  projects: [
    {
        testMatch: ['testMe.test.ts'],
        setupFiles: ['jest-setup.js']
    }
  ]
}

但单独为一个 test 文件写 projects 配置比较麻烦。如果不想用 projects,还有一个办法。

前面说到 import 会被提升到代码前面,但只要我们的代码也放在一个单独的模块里,然后从 test 文件里 import 就行了:

import './testMe-prepare' // 这个文件里包含设置全局变量的代码
import { testMe } from './testMe'