项目中的荒草地
我们经常只关注业务代码的代码质量,而忽略了测试用例的代码质量。这让单元测试代码狂野生长。从而导致单元测试代码往往不起作用。这也是有些程序员认为单元测试代码没什么用。所以我会分享我在项目中发现的jest单元测试最佳实践。
从代码风格开始
让我们从测试代码的风格开始吧。我们一般都会用eslint来规范业务代码。你是否尝试过使用eslint来规范单元测试代码呢?试试看 eslint-plugin-jest
吧。这是这个项目的页面https://www.npmjs.com/package/eslint-plugin-jest
如果访问速度太慢,可以使用淘宝NPM镜像。
这些是我在项目中使用的规则列表
'jest/expect-expect': 'error',
'jest/lowercase-name': [
'error',
{
ignore: ['describe'],
},
],
'jest/no-disabled-tests': 'error'
'jest/no-done-callback': 'error',
'jest/no-duplicate-hooks': 'error',
'jest/no-conditional-expect': 'error',
'jest/no-export': 'error',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/no-interpolation-in-snapshots': 'error',
'jest/no-jasmine-globals': 'error',
'jest/no-jest-import': 'error',
'jest/no-large-snapshots': 'error',
'jest/no-mocks-import': 'error',
'jest/no-standalone-expect': 'error',
'jest/no-test-prefixes': 'error',
'jest/valid-describe': 'error',
'jest/valid-expect-in-promise': 'error',
'jest/prefer-to-have-length': 'warn',
'jest/valid-expect': 'error',
大部分规则从名字上都很容易理解。我想重点介绍其中几个规则
jest/no-done-callback
如果你喜欢用done
来测试异步代码。你可能要改变你的代码习惯了。不要再用done
了。因为一旦代码有什么错误造成无法执行到done所在的代码。单元测试不会失败,而是会超时。而且使用回调函数来调用done
也让代码晦涩难懂。
以下是我们可能会使用done
的场景
测试异步操作
如果你要测试异步操作。请返回Promise对象。比如
return doSomething().then(data => {...})
如果你可以用async...await
那么就使用它。async...await
让代码更容易理解。
测试setTimeout
如果你想测试 setTimeout
. 你可以在单元测试文件的开头使用 jest.useFakeTimers()
然后在你调用了业务代码后调用 jest.runAllTimers()
。jest.runAllTimers
可以快速的跳过你设置的等待时间。比如
doOperationAfterTime();
jest.runAllTimers();
expect(1).toBe(1);
如果你想了解 timer mocker 的更多用法,可以参考 https://jestjs.io/docs/timer-mocks.
jest/no-conditional-expect
在条件代码中使用 expect
可能会导致 expect
被无声无息的跳过。把 expect
放到 catch
同样不是个好主意。这同样会导致expect
被跳过。
以下是坏的例子
it ('foo', () => {
const result = doSomething();
if (result === 1) {
expect(1).toBe(1)
}
})
it ('bar', () => {
try {
await foo();
} catch (err) {
expect(err).toMatchObject({ code: 'MODULE_NOT_FOUND' });
}
})
这些是好的例子
it ('foo', () => {
const result = doSomething();
expect(result).toBe(1);
expect(1).toBe(1);
})
it ('throws an error', () => {
await expect(foo).rejects.toThrow(Error);
})
jest/no-identical-title
no-identical-title
是个简单但是非常有用的规则。它的作用是防止两个测试用例使用同样的名字
以下是坏的例子
it('should do bar', () => {});
it('should do bar', () => {}); // Has the same title as the previous test
我曾经不止一次有过这样的经验。当单元测试失败的时候,我花了很久的时间调试,但是它们依然失败。最后我才意识到我调试的单元测试并不是失败的那个。这很让人沮丧,尤其是当两个有着相同名字的测试用例都失败的时候。
这是我的这篇博文的英文版:https://dev.to/alexxiyang/jest-best-practice-1-use-eslint-plugin-jest-o7e