【Next.js】Jest+React Testing Libraryを導入したい

# はじめに
公式ドキュメントを読んでも導入方法までは詳しく記載されていないようで、参考記事も多く漁ったのですが古かったり環境が違ったり動かなかったりと、導入するまでに時間がかかってしまったのでまとめてみたいと思います

※この記事の設定のままだとプロダクトコードのコンパイル時にNext.jsがbabelの設定を参照してしまい、コードが動作しない場合もあります。その際は以下の補足記事をご参考ください。

https://qiita.com/does_not_exist/items/6ad2edc937daba63b33b

# 環境
- "next": "^13.4.2"
- "react": "^18.2.0"
- "jest": "^29.7.0"
- "@testing-library/react": "^14.2.1"



# 導入手順(プロジェクトが作成されている前提)


JestとReact Testing Library、その他テスト実行に必要なパッケージをインストールします

```
yarn add --dev jest @testing-library/react @babel/preset-env @babel/preset-react jest-environment-jsdom @testing-library/jest-dom
```

次にテストとテスト用コンポーネントをシンプルに作成します

```jsx:project/frontend/__tests__/test.spec.jsx
import { render, screen } from '@testing-library/react';
import { Sample } from '../components/sample';
import '@testing-library/jest-dom'

describe('test', () => {
    it('testsimasu', () => {
        render(<Sample />);
        const paragraphElement = screen.getByTestId('test-paragraph');
        expect(paragraphElement).toBeInTheDocument();
        expect(paragraphElement).toHaveTextContent('Hello');
    });
    
});

```

```jsx:project/frontend/components/sample.jsx
import React from 'react'

export const Sample = () => {
    return (
    <div>
        <p data-testid='test-paragraph'>Hello</p>
    </div>
    )
}

```

Jestは実行時に```__tests__```を見に行ってくれるようなので、その配下にテストファイルを作成しています


package.jsonに以下を追加して```yarn test```でJestを実行するようにしています

```diff:package.json
  "scripts": {
    "dev": "next dev",
    "output": "next build && next export",
    "build": "next build",
    "start": "next start",
    "export": "next export",
    "lint": "next lint",
+   "test": "jest"
  }

```

また、Jestが.jsxファイルを読み込むには以下の設定ファイルを作成する必要がありました(後述)

```js:project/fronend/jest.config.js
module.exports = {
  "transform": {
    '^.+\\.(js|jsx)$': 'babel-jest',
  },
  "testEnvironment": "jsdom",
  "modulePaths": ['<rootDir>/../node_modules'],
  "moduleNameMapper": {
    '^@hoges/(.*)$': '<rootDir>/hogehoge/$1',
 },
};

```

```js:project/fronend/babel.config.js

module.exports = {
    presets: [
        ["@babel/preset-env", { targets: { node: "current" } }],
        ["@babel/preset-react", { "runtime": "automatic" }]
    ],
};

```

ここまでの状態で```yarn test```を実行すると通りました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/482e1975-d8b9-caa7-aedc-87426868887e.png)

# 詰まったエラーたち

### Jest encountered an unexpected token
```
Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

中略

 SyntaxError: /project/frontend/components/sample.jsx: Support for the experimental syntax 'jsx' isn't currently enabled (5:5):
```

JestがJSX構文をサポートしていないため、ReactのようなESModules形式で記述されているファイルは読み込めないようでした

読み込むためには```ts-jest```や```babel-jest```を使用してJSXをJavaScriptに変換する必要があります
今回はTypeScript環境ではないので後者を使用しています

この```babel-jest```くんがESModules形式で記述されたファイルをCommonJS形式に変換してくれるようなのですが、Jestくんとbabel-jestくんにそれぞれ設定が必要になります

- Jestがbabel-jestを使用できるように(jest.config.js)
```js
module.exports = {
  transform: {
    '^.+\\.(js|jsx)$': 'babel-jest',
  },
};
```
- babel-jestがESModules形式をCommonJS形式に変換できるように(babel.config.js)
```js
module.exports = {
  presets: ['@babel/preset-react'],
};
```

設定ファイルを作成したら```yarn add --dev @babel/preset-react```

### Cannot find module 'react'
Jestがreactモジュールを見つけられないというエラーです

プロジェクトのnode_modules配下を参照できるようにpathを通してあげました
```<rootDir>```はここではproject/frontendになります
node_modulesはfrontendと同じ階層になるので以下のようなpathを設定しています

```js
"modulePaths": ['<rootDir>/../node_modules'],
```

### Cannot find module '@hogehoge/use_hoge.js'
先ほどと同じようにそんなモジュールは知らないというエラーです
モジュールをnode_modules外に自作していたので新たにpathの設定をしました

例えば```project/frontend/hogehoge```にある```use_hoge.js```モジュールを

```import Hoge from '@hoges/use_hoge.js';```としてテスト用コンポーネントでインポートしているとき、
以下のようにマッパーすることで参照できました

```js
  "moduleNameMapper": {
    '^@hoges/(.*)$': '<rootDir>/hogehoge/$1',
 }
```

プロダクトコードを変えたくない場合はこの設定が使えそうです



# おわりに
想定より時間がかかってしまいましたが、なんとか導入できたので良かったです
必要なパッケージをインストールした後に自分で設定しないといけない部分が多かったので苦労しました