🧪

Vite+React+TypeScriptに テスト環境 Jest+TesttingLibrary をステップbyステップで作る

2022/02/19に公開

前回 React の テスト方針を調べたでテスト環境について調べました。

今回は、Vite + React + TypeScript + EsLint + Prettier に
テスト環境を Jest + Testting Library を ステップbyステップで作っていきます。

環境作成

まずは Viteを使って、React + TypeScript を作成し、EsLint + Prettier を導入した状態までのプロジェクトを用意します。

環境作成の詳細
  • Viteを使用したアプリの作成方法 → 参考
  • EsLint + Prettier の導入方法 → 参考1参考2

環境作成用のコマンド
Viteを使用してReact(TypeScript)アプリを、「testsample-app」というアプリ名で作成します。

npm init vite testsample-app -- --template react-ts
cd testsample-app
npm install

npm install eslint --save-dev
npx eslint --init
    // eslint 設定値
    ? How would you like to use ESLint? (Use arrow keys)
        > To check syntax, find problems, and enforce code style
    ? What type of modules does your project use? (Use arrow keys)
        > None of these
    ? Which framework does your project use? 
        > React
    ? Does your project use TypeScript? 
        > Yes
    ? Where does your code run?  
        >  √ Browser
    ? How would you like to define a style for your project? (Use arrow keys)
        > Use a popular style guide
    ? Which style guide do you want to follow? (Use arrow keys)
        > Airbnb: https://github.com/airbnb/javascript
    ? What format do you want your config file to be in? (Use arrow keys) 
        > YAML
    ? Would you like to install them now with npm?
        > Yes

npm install eslint-config-airbnb-typescript --save-dev
npm install eslint-import-resolver-typescript --save-dev

npm install prettier --save-dev
npm install eslint-config-prettier --save-dev

code .

.eslintrc.ymlファイルを修正
rulesはお好みで変更してください。

.eslintrc.yml
env:
  browser: true
  es2021: true
parser: '@typescript-eslint/parser'
parserOptions:
  ecmaFeatures:
    jsx: true
  ecmaVersion: latest
  project: ./tsconfig.json
plugins:
  - react
  - react-hooks
  - '@typescript-eslint'
extends:
  - plugin:react/recommended
  - plugin:react-hooks/recommended
  - airbnb
  - airbnb-typescript
  - prettier
ignorePatterns:
    - vite.config.ts
rules:
  # Reactのインポートをチェックしない
  react/react-in-jsx-scope: off
  # セミコロンつけない
  semi:
    - error
    - never
  # デフォルトエクスポートをエラーにする
  import/prefer-default-export: off
  import/no-default-export: error

.prettierrc.ymlファイルを作成する
こちらもお好みで変更してください。

.prettierrc.yml
tabWidth: 2
singleQuote: true
trailingComma: 'none'
semi: false
useTabs: false

package.jsonファイルのscriptタグに追加

package.json
  "scripts": {
   ・・・中略・・・
    "lint": "eslint --ext .tsx,.ts src/",
    "lintfix": "eslint --fix --ext .tsx,.ts src/",
    "format": "prettier --write \"**/*.+(js|json|yml|ts|tsx)\""
  },

VsCodeの拡張機能、EsLint、Prettier をインストールする。

npm run lintを行い、エラー箇所(上記の設定の場合は2か所)を修正する。

App.tsx
- <button type="button" onClick={() => setCount((count) => count + 1)}>
+ <button type="button" onClick={() => setCount((precount) => precount + 1)}>

ESLintでDefault Exportをエラーにしているので、App.tsxとmain.tsxがエラーになります。Named Export に修正します。

App.tsx
- function App() {
+ export function App() {
  const [count, setCount] = useState(0)
  ・・・中略・・・
  }

- export default App
main.tsx
- import App from './App'
+ import { App } from './App'

テスト環境を作成

「create-react-app」でプロジェクトを作成した場合、下記のものはデフォルトでインストールされています。
@types/jest
@testing-library/react
@testing-library/jest-dom
@testing-library/user-event

Viteで作成した場合は何も入っていませんので一からインストールしていきます。
ステップbyステップでインストールしていきますが、最後の「まとめ」に、今回インストールしたものとその設定値をまとめて載せています。

バージョン情報です。

package.json
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^5.16.2",
    "@testing-library/react": "^12.1.3",
    "@testing-library/react-hooks": "^7.0.2",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.4.0",
    "@types/react": "^17.0.33",
    "@types/react-dom": "^17.0.10",
    "@typescript-eslint/eslint-plugin": "^5.12.0",
    "@typescript-eslint/parser": "^5.12.0",
    "@vitejs/plugin-react": "^1.0.7",
    "eslint": "^8.9.0",
    "eslint-config-airbnb": "^19.0.4",
    "eslint-config-airbnb-typescript": "^16.1.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-import-resolver-typescript": "^2.5.0",
    "eslint-plugin-import": "^2.25.4",
    "eslint-plugin-jest": "^26.1.1",
    "eslint-plugin-jest-dom": "^4.0.1",
    "eslint-plugin-jsx-a11y": "^6.5.1",
    "eslint-plugin-react": "^7.28.0",
    "eslint-plugin-react-hooks": "^4.3.0",
    "eslint-plugin-testing-library": "^5.0.5",
    "jest": "^27.5.1",
    "prettier": "^2.5.1",
    "ts-jest": "^27.1.3",
    "typescript": "^4.5.4",
    "vite": "^2.8.0"
  }

Jest関係のインストール

まずは Jest のインストールからです。
参考にしたのはコチラ:https://www.robinwieruch.de/react-testing-jest/
Jest 本体、TypeScriptを使用しているのでJestの型定義である @types/jest をインストールします。
また、Jest はテストの実行時にテストコードの型検査を行いません。
テストコードの型検査を行うため TypeScript から JavaScript へ変換するためのトランスパイラ ts-jest をインストールします。
※検索すると ts-jest より esbuild-jest のほうが早いなどの情報がありますが、esbuild-jest は開発がストップしているため、jest は v27 で大幅な変更が入ったようですがその変更に対応していないようです。(2022/02/19時点の Jest バージョンはv27.5.1)

npm install jest --save-dev
npm install @types/jest --save-dev
npm install ts-jest --save-dev

Jest設定ファイルの作成

つづいて Jest 設定ファイル「jest.config.json」を作成します。

roots 設定で、Jest がファイルを検索するフォルダを指定します。
testMatch 設定で、テストするファイルを正規表現で指定しています。
transform 設定で、ts/tsx ファイルに対して ts-jest を使うように指定します。

jest.config.json
{
  "roots": [
    "<rootDir>/src"
  ],
  "testMatch": [
    "**/__tests__/**/*.+(ts|tsx|js)",
    "**/?(*.)+(spec|test).+(ts|tsx|js)"
  ],
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  }
}

package.jsonを設定

package.json の scriptsに "test" と "test:watch" を追加します。
watch の指定は、ソースコード変更するたびにテストが実行されるようにするものです。

package.json
  "scripts": {
    ・・・中略・・・
    "test": "jest --config ./jest.config.json",
    "test:watch": "npm run test -- --watch"
  },

動作確認

動作確認として関数テストを作成してみます。

src/myFunc.tsにmyFunc関数を定義します。

src/myFunc.ts
export function myFunc(a:number, b:number) {
  return a + b
}

src/myFunc.test.tsにテストを作成します。

src/myFunc.test.ts
import { myFunc } from './myFunc'

test('adds 1 + 2 to equal 3', () => {
  expect(myFunc(1, 2)).toBe(3)
})

describe('true is truthy and false is falsy', () => {
  test('true is truthy', () => {
    expect(true).toBe(true)
  })

  test('false is falsy', () => {
    expect(false).toBe(false)
  })
})

testコマンドを実行してみます。

npm run test

テストにパスしました。

Testting library関係のインストール

つづいてTestting libraryをインストールしていきます。

  • @testing-library/react
    React コンポーネントテストのためのユーティリティライブラリーです。
  • @testing-library/jest-dom
    Jest のマッチャーを拡張するユーティリティライブラリーです。
  • @testing-library/user-event
    実際のイベントをシミュレートするためのユーティリティライブラリーです。
  • @testing-library/react-hooks
    React が提供する Hooks テストのためのユーティリティライブラリーです。
npm install @testing-library/react --save-dev
npm install @testing-library/jest-dom --save-dev 
npm install @testing-library/user-event --save-dev
npm install @testing-library/react-hooks --save-dev

動作確認

動作確認としてコンポーネントのテストを作成してみます。
src/MyComponent.tsxにMyComponentコンポーネントを定義します。

src/myFunc.ts
export function MyComponent() {
  const title = 'Hello Test'
  return (
    <div>
      <p>{title}</p>
    </div>
  )
}

src/MyComponent.test.tsにテストを作成します。
ドキュメント内に「Hello Test」が含まれているかをテストします。

src/myFunc.test.ts
import { render, screen } from '@testing-library/react'
import { MyComponent } from './MyComponent'

test('「Hello Test」が描画されている', () => {
  render(<MyComponent />)
  screen.debug()
  expect(screen.getByText('Hello Test')).toBeInTheDocument()
})

testコマンドを実行してみます。

npm run test

エラーが発生しました。😢

The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string.
    Consider using the "jsdom" test environment.

Jest v27 から testEnvironment の仕様が変わり、デフォルト値が "jsdom" から "node "に変更になりました。
DOM 環境をテストする場合は testEnvironment 設定に "jsdom" を指定する必要があります。
参考:Jest 27: Jest の新しいデフォルト設定 - 2021 年版

jest.config.json に testEnvironment 設定を追加します。

jest.config.json
{
  "roots": [
    "<rootDir>/src"
  ],
  "testMatch": [
    "**/__tests__/**/*.+(ts|tsx|js)",
    "**/?(*.)+(spec|test).+(ts|tsx|js)"
  ],
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
+  "testEnvironment": "jsdom"
}

再度 testコマンドを実行してみます。

npm run test

エラーが発生しているようです。😵

TypeError: expect(...).toBeInTheDocument is not a function          

@testing-library/jest-dom usageにちゃんと書いていますね。
テストファイルに毎回 import '@testing-library/jest-dom'を書くか、設定ファイルをつくって tsconfig で読み込むといいようです。

テストファイルに毎回 import を書くのは嫌なので、設定ファイルを作ります。
jest.setup.ts ファイルを作成します。

jest.setup.ts は EsLint が怒ってきますが今は無視しておきます。

jest.setup.ts
import '@testing-library/jest-dom/extend-expect'

jest.config.json の setupFilesAfterEnv に先ほどのファイルを設定します。
setupFilesAfterEnv 設定には、各テストファイルが実行される直前に実行したいスクリプトファイルを指定します。

jest.config.json
{
  "roots": [
    "<rootDir>/src"
  ],
  "testMatch": [
    "**/__tests__/**/*.+(ts|tsx|js)",
    "**/?(*.)+(spec|test).+(ts|tsx|js)"
  ],
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
  "testEnvironment": "jsdom",
+  "setupFilesAfterEnv": ["<rootDir>/jest.setup.ts"]
}

tsconfig.tsで読み込みます。

tsconfig.ts
{
  "compilerOptions": {
    ・・・略・・・
  },
-  "include": ["src"],
+  "include": ["src", "jest.setup.ts"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

再度 test コマンドを実行してみます。

npm run test

テストに成功しました。

EsLintの設定

Jest 用の EsLint のプラグイン、jest-dom 用の EsLint プラグイン、testing-library 用のEsLint プラグインをインストールしておきます。

npm install eslint-plugin-jest --save-dev
npm install eslint-plugin-jest-dom --save-dev
npm install eslint-plugin-testing-library --save-dev

.eslintrc.ymlに設定値を加えます。
plugins に、jest、jest-dom、testing-library を追加します。
extends は、テストファイルにだけ設定値を適用したいので、overrides で指定します。
files で指定するテストファイルは、「Jest設定ファイルの作成」で作成した jest.config.json の testMatch 設定の値を指定します。

.eslintrc.yml
env:
  browser: true
  es2021: true

parser: '@typescript-eslint/parser'
parserOptions:
  ecmaFeatures:
    jsx: true
  ecmaVersion: latest
  project: ./tsconfig.json
plugins:
  - react
  - react-hooks
  - '@typescript-eslint'
+  - jest
+  - jest-dom
+  - testing-library
extends:
  - plugin:react/recommended
  - plugin:react-hooks/recommended
  - airbnb
  - airbnb-typescript
  - prettier
+ overrides:
+  - files:
+      - '**/__tests__/**/*.+(ts|tsx|js)'
+      - '**/?(*.)+(spec|test).+(ts|tsx|js)'
+    extends:
+      - plugin:jest/recommended
+      - plugin:jest-dom/recommended
+      - plugin:testing-library/react
ignorePatterns:
    - vite.config.ts
rules:
  # Reactのインポートをチェックしない
  react/react-in-jsx-scope: off
  # セミコロンつけない
  semi:
    - error
    - never
  # デフォルトエクスポートをエラーにする
  import/prefer-default-export: off
  import/no-default-export: error

以上でテスト環境の作成がおわりました。
今回もいろいろとインストールと設定があり、なかなか面倒でした。

まとめ

インストールした内容とその設定をまとめて記載しておきます。

インストール

npm install jest --save-dev
npm install @types/jest --save-dev
npm install ts-jest --save-dev

npm install @testing-library/react --save-dev
npm install @testing-library/jest-dom --save-dev 
npm install @testing-library/user-event --save-dev
npm install @testing-library/react-hooks --save-dev

npm install eslint-plugin-jest --save-dev
npm install eslint-plugin-jest-dom --save-dev
npm install eslint-plugin-testing-library --save-dev

設定ファイル

package.jsonに設定追加

package.json
  "scripts": {
    ・・・中略・・・
+    "test": "jest --config ./jest.config.json",
+    "test:watch": "npm run test -- --watch"
  },

jest.setup.tsを新規作成

jest.setup.ts
import '@testing-library/jest-dom/extend-expect'

jest.config.jsonを新規作成

jest.config.json
{
  "roots": [
    "<rootDir>/src"
  ],
  "testMatch": [
    "**/__tests__/**/*.+(ts|tsx|js)",
    "**/?(*.)+(spec|test).+(ts|tsx|js)"
  ],
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
  "testEnvironment": "jsdom",
  "setupFilesAfterEnv": ["<rootDir>/jest.setup.ts"]
}

tsconfig.tsに設定追加

tsconfig.ts
{
 "compilerOptions": {
   ・・・略・・・
 },
+  "include": ["src", "jest.setup.ts"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

.eslintrc.ymlに設定追加

.eslintrc.yml
env:
 browser: true
 es2021: true
parser: '@typescript-eslint/parser'
parserOptions:
 ecmaFeatures:
   jsx: true
 ecmaVersion: latest
 project: ./tsconfig.json
plugins:
 - react
 - react-hooks
 - '@typescript-eslint'
+ - jest                  
+  - jest-dom             
+  - testing-library      
extends:
 - plugin:react/recommended
 - plugin:react-hooks/recommended
 - airbnb
 - airbnb-typescript
 - prettier
+ overrides:               
+  - files:
+      - '**/__tests__/**/*.+(ts|tsx|js)'
+      - '**/?(*.)+(spec|test).+(ts|tsx|js)'
+    extends:
+      - plugin:jest/recommended
+      - plugin:jest-dom/recommended
+      - plugin:testing-library/react
ignorePatterns:
   - vite.config.ts
rules:
 # Reactのインポートをチェックしない
 react/react-in-jsx-scope: off
 # セミコロンつけない
 semi:
   - error
   - never
 # デフォルトエクスポートをエラーにする
 import/prefer-default-export: off
 import/no-default-export: error

追記

2022/05/24 テスト環境 Vitest に関する記事を書きました。
Vite であれば検討する価値はあると思います!
Vite + React + TypeScript に テスト環境 Vitest をステップbyステップで作る

Discussion