テストコードにもLinterを導入しよう

## はじめに

この記事はニジボックスQiita記事投稿リレーの１日目の記事として書きました。
きっかけはSlackで「投稿リレーやってみたい」と呟いたらいい感じにメンバーが集って実際に投稿リレーをやることとなりました。
思いつきの企画にも乗ってくれるメンバーがいる環境に感謝したいです。
自分の投稿のあとにも記事が公開されると思いますので、もしよろしければ今後公開される記事も見ていただけると幸いです。

## 本題

最近ではフロントエンドでもコンポーネントの単体テスト・結合テストなどが一般的になりましたが、どのようにテストコードを書けばいいのか自信がない人も多いかと思います。

自分もあまりテストコードを書くことに自信があるという訳ではなかったのですが業務で単体テストを書くことになり、なるべく質の高いテストコードを書けるよう[eslint-plugin-jest](https://www.npmjs.com/package/eslint-plugin-jest)というjest向けのESLintプラグインも導入しました。
推奨ルールからいくつかカスタマイズした部分もあるので、カスタマイズ内容とその理由についてまとめさせていただきます。

## eslint-jestとは

前記の通り、ESLintのjest向けのプラグインです。
主にテストコードの書き方を統一するために導入しました。

https://www.npmjs.com/package/eslint-plugin-jest

## ルールのカスタマイズについて

推奨のルール（`jest/recommended`）だけでも良かったのですが、テストの書き方を共通化してレビュー時の負担を減らす意味合いでも追加でルールを定義して制約を強めることにしました。

```js:eslintrc.js
// ...
overrides: [
  {
    files: ['**/*.test.tsx', '**/*.test.ts'],
    plugins: ['jest'],
    env: {
      'jest/globals': true,
    },
    extends: ['plugin:jest/recommended'],
    // https://github.com/jest-community/eslint-plugin-jest#rules
    rules: {
      // expectは最大2つまで
      'jest/max-expects': ['error', { max: 2 }],
      // test/itではtestを利用する
      'jest/consistent-test-it': ['error', { fn: 'test', withinDescribe: 'test' }],
      // テストケースは必ずdescribeの中に定義されている必要がある
      'jest/require-top-level-describe': ['error'],
      // describeは3回以上ネストしてはいけない
      'jest/max-nested-describe': ['error', { max: 3 }],
      // hooksは必ずテストケースの前に記述する必要がある
      'jest/prefer-hooks-on-top': ['error'],
      // 関数をモックする場合 jest.spyOn() を優先的に使用するようにする
      'jest/prefer-spy-on': ['error'],
      // 組こ込みのMatcherが使える場合は自動修正を提案する
      'jest/prefer-equality-matcher': ['warn'],
    },
  },
],
// ...
```

上記が実際の`eslint-jest`の設定です。
コメントも入れたのでざっくりと眺めてもらうだけでもなんのためのルールなのかというのが分かるかもしれませんが、中でもテスト対象の明確化とテストの構造化のためのルールが個人的にはコアな部分になると思っているのでそちらについて紹介させていただきます。

### テスト対象を明確にするためのルール

一つのテストコードで複数のアサーションを行うと、何に対してのテストを行っているかが分かりづらくなってしまいます。
`jest/max-expects`はテストコード内で使用できる`expects`の最大数を制限できるルールで今回は2個までを上限として設定しました。

```js
// expectは最大2つまで
'jest/max-expects': ['error', { max: 2 }],
```

このルールを導入することで、１つのテストコードにどんどんアサーションが追加されていき何に対してテストを行っているのかが分からなくなってしまう、という状態を防ぐことができます。

### テストの構造化について

下記の2項目は主にテストの構造化についての制約としてルールを追加しました。

```js
// テストケースは必ずdescribeの中に定義されている必要がある
'jest/require-top-level-describe': ['error'],
// describeは3回以上ネストしてはいけない
'jest/max-nested-describe': ['error', { max: 3 }],
```

このルールによりテストコードは１つ以上３つ以下の`describe`の中にしか記載できなくなります。
単体テストは`storybook`+ `testing-library` + `jest`の組み合わせで主にコンポーネントに対して行うため下記のようなテストの構造を理想としてこのようなルールを設けました。

```index.test.ts
import { render, screen } from '@testing-library/react'
import { composeStories } from '@storybook/react'
import * as stories from './index.stories'
import '@testing-library/jest-dom/extend-expect'

describe('<機能名>の機能を提供する<コンポーネント名>コンポーネント', () => {
  const { Default } = composeStories(stories)

  // テストの対象などによってまとめる
  // ex) DOMの存在確認やイベントハンドラーの実行確認など
  describe('<テストカテゴリー>', () => {
    test('<テスト内容>', () => {
      render(<Default />)
      expect(true).toBe(false)
    })
    test('<テスト内容>', () => {
      render(<Default />)
      expect(true).toBe(false)
    })
  })
})
```

### その他の項目

その他の項目としては`test()`と`it()`どちらを使うか、組み込みのマッチャーが使える場合は修正を提案するなど、テストコードの書き方を統一するためのルールなども導入しました。

ルールについてはリポジトリの[README.md](https://github.com/jest-community/eslint-plugin-jest#rules)に記載されているためチームのテスト方針に合ったルールが無いかなど見てみるといいかもしれません。

https://github.com/jest-community/eslint-plugin-jest#rules

このようにテストコードにある程度の制約を設けることで、ある程度読みやすいテストコードを書けるようになったと感じています。
また、単純にコードのクオリティアップという側面だけでなく自分自身でテストコードの構造化や何を保証するためのテストコードなのか、という部分も考える良いきっかけになったと感じています。

::: note
この記事はニジボックス投稿リレー企画、１日目の記事です。
次の投稿は[@ment_RE](https://qiita.com/ment_RE)さんの「【TypeScript】超実践的テクニック集【Reactなし】」です！
:::