Jest Best Practice 1: Use eslint-plugin-jest
Tags: javascript, jest, eslint
Published: 2021-04-28 03:22:47

# 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.