Wasteland in the project
We used to focus on the quality of business code only and ignore the unit test code quality. That makes the unit tests code becomes the wild west of our project. So I gonna share some practice I've used in my project.
Start with the code style
First, let's begin with the style of your unit test code. We used to use eslint on our business code. But have you try to use eslint on the jest code? Try eslint-plugin-jest
. Here is the introduction of this package: https://www.npmjs.com/package/eslint-plugin-jest
Here are the rule set I've used in my project
'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',
Most of them are easy to understand. But I want to introduce some of them.
jest/no-done-callback
We might need to change our habits. Don't use done
anymore. Because if the code is not able to reach done
it will easily get an error. Also using callback can be very prone, as it requires careful understanding of how assertions work in tests, or otherwise tests won't behave as expected.
There are 2 scenarios we need to change our way to write the test
for async operation
For async operation. Turn to use async...await
instead of done. Or you can return the Promise like
return doSomething().then(data => {...})
for setTimeout
For setTimeout
. Use jest.useFakeTimers()
at the beginning of the test file. Then use jest.runAllTimers()
to fast-forward until all timers have been executed
For more information about timer mocker, please refer to https://jestjs.io/docs/timer-mocks.
jest/no-conditional-expect
Use the expect
in conditional call could lead to the expect
silently being skipped. Put the expect
in catch
is also easy to be skipped.
The following patterns are warnings:
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 will be better to write these tests in this way
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
There is an important rule no-identical-title
. It's for preventing us to name 2 test cases with the same name.
The following patterns are considered warnings:
it('should do bar', () => {});
it('should do bar', () => {}); // Has the same title as the previous test
It's simple but very useful. I have the experience that I tried to fix the failed unit test. But it was still failed after 30mins troubleshooting. Then I found that I'm not fixing the one that failed. It's especially tricky when there are 2 failed unit tests with the same name.
Top comments (1)
Jest best practice 2: use
findRelatedTests
command in your pre-commit hook. See ore about how it works in my post: dev.to/srshifu/under-the-hood-how-...