Angular13でテストフレームワークをJestに移行した時の備忘録

## はじめに
AngularのテストフレームワークをデフォルトのJasmine + KarmaからJestへ変更した際の備忘録です。

カバレッジについては調査中。良い記事があればコメントなどで教えてもらえると嬉しいです。

※Dockerで環境構築しています。
## 確認時のバージョン
```bash
_                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 13.3.9
Node: 14.15.0
Package Manager: npm 6.14.8
OS: linux arm64

Angular: 13.3.11
... animations, common, compiler, compiler-cli, core, forms
... language-service, localize, platform-browser
... platform-browser-dynamic, router

Package                            Version
------------------------------------------------------------
@angular-devkit/architect          0.1303.9
@angular-devkit/build-angular      13.3.9
@angular-devkit/core               13.3.9
@angular-devkit/schematics         13.3.9
@angular/cdk                       13.3.9
@angular/cli                       13.3.9
@angular/material                  13.3.9
@angular/material-moment-adapter   13.3.9
@schematics/angular                13.3.9
rxjs                               6.5.5
typescript                         4.6.4
```

## Jest導入手順
1.  [@types/jest](https://www.npmjs.com/package/@types/jest)、[jest-preset-angular](https://thymikee.github.io/jest-preset-angular/)をインストールする。
```bash
npm i -D jest @types/jest jest-preset-angular
```


2\. Jest設定ファイルを追加する。
```bash
touch jest.config.js src/setup-jest.ts
```


3\. `jest-preset-angular`プリセットを `jest.config.js` から読み込む。

```jest.config.js
require('jest-preset-angular');

module.exports = {
    preset: 'jest-preset-angular',
    setupFilesAfterEnv: ['<rootDir>/src/setup-jest.ts'], // 後述する@angular-builders/jest導入時は設定が被るので不要になる
    moduleNameMapper: {
        '^src/(.*)$': '<rootDir>/src/$1',
      },
}
```

4\. `jest-preset-angular/setup-jest` を `src/setup-jest.ts` からインポートする。

```setup-jest.ts
import 'jest-preset-angular/setup-jest';
```

5\. `tsconfig.spec.json` を修正する。
```tsconfig.spec.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/spec",
    "types": [
      "jest" // jestに変更
    ]
  },
  "files": [
    "src/setup-jest.ts"  // 追加したsetup-jest.tsに変更
  ],
  "include": [
    "src/**/*.spec.ts",
    "src/**/*.d.ts"
  ]
}
```
6\.  不要になったパッケージをアンインストール、古い設定ファイル`karma.conf.js`、`src/test.js`を削除する。
```bash
rm karma.conf.js src/test.ts
```
```bash
npm uninstall jasmine-core karma karma-chrome-launcher karma-coverage-istanbul-reporter karma-jasmine karma-mocha-reporter @types/jasmine @types/jasminewd2 karma-jasmine-html-reporter
```
※`jasmine-spec-reporter`はprotractor.conf.jsで使用していたので一旦保留。

7\.  下記コマンド実行で、既存のテストコードをJestに移行します。
```bash
npx jest-codemods
```

8\. `npx jest`でテストが実行されるか確認する。

## `ng test`コマンドの設定
1\.  [@angular-builders/jest](https://www.npmjs.com/package/@angular-builders/jest)をインストールする。
    `@angular-builders/jest`はjest-preset-angularを使ったテストを`ng test`コマンド経由で実行できるようにするパッケージです。
```bash
npm i -D @angular-builders/jest
```
2\. angular.json内の`test`コマンドを下記に書き換える。
```angular.json
        "test": {
          "builder": "@angular-builders/jest:run",
          "options": {
            "configPath": "./jest.config.js"
          }
        }
```
- `configPath`  ：相対的な jest 構成ファイルへのパスを指定

3\. `ng test`コマンドでJestのテストが実行可能になる。

### `ng test`時にimportでエラーが発生する
```
Cannot set base providers because it has already been called
import 'jest-preset-angular/setup-jest';
```
- `src/setup-jest.ts`と`node_modules/@angular-builders/jest/dist/jest-config/setup.js`のimportが被っていたため発生しました。
- 今回は`src/setup-jest.ts`が１行のみだったので、`jest.config.js`のsetupFilesAfterEnvを指定しない形で修正しました。
```jest.config.js
require('jest-preset-angular');

module.exports = {
    preset: 'jest-preset-angular',
    //setupFilesAfterEnv: ['<rootDir>/src/setup-jest.ts'],
    moduleNameMapper: {
        '^src/(.*)$': '<rootDir>/src/$1',
      },
}
```

## ファイル単位またはディレクトリ単位でのテスト
- テスト実行したいファイルまたはディレクトリのパスを`npx jest`の末尾に指定する。
```bash
npx jest src/app/pages/xxx/xxx.component.spec.ts
```
- `@angular-builders/jest`使用時は下記で実行可能。
```bash
ng test --test-path-pattern=${file_path}
```

### zone.jsのバージョンを0.11.1以降にアップデート
- v0.11.1より以前はファイル構成が異なりエラーになるため、v0.11.1以降にアップデートを行う。
    - v0.11.1以降、Zone.jsはAngularPackageFormatに準拠している。

## launch.jsonにJestテスト用のデバッグ構成を追加する
- launch.jsonにデバッグ構成を追加するため、devcontainer.json > launch > configurationsに下記を追加します。
```devcontainer.json
{
    ...
    "launch": {
      "configurations": [
        ...
        { // Jest：全ファイルデバッグ
          "type": "node",
          "request": "launch",
          "name": "Jest All",
          "program": "${workspaceFolder}/node_modules/@angular/cli/bin/ng",
          "cwd": "${workspaceFolder}",
          "args": [
            "test",
            "--testMatch=\"**/+(*.)+(spec|test).+(ts|js)?(x)\"",
            "--runInBand"
          ],
          "console": "integratedTerminal",
          "internalConsoleOptions": "neverOpen",
        },
        { // Jest：単一ファイルデバッグ
          "type": "node",
          "request": "launch",
          "name": "Jest Current File",
          "program": "${workspaceFolder}/node_modules/@angular/cli/bin/ng",
          "cwd": "${workspaceFolder}",
          "args": [
            "test",
            "--testMatch=\"**/+(*.)+(spec|test).+(ts|js)?(x)\"",
            "--test-path-pattern=${fileBasenameNoExtension}",
            "--runInBand"
          ],
          "console": "integratedTerminal",
          "internalConsoleOptions": "neverOpen",
        }
      ]
    },
}
```

実行とデバッグからテスト実行が可能になります。
![vscode-debug.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985483/8aab5ed9-4aac-c4fc-8fef-dcd3ad32225c.jpeg)


## VSCode拡張機能 ※調査中
- [JestRunner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner)の使用を試していたが、実行時のargsがキャメルケースで出力されてしまう。`Error: Unknown arguments: runInBand`とエラーになってしまうため、Angularでは使用できないと断念した。
	- angular-buildersでキャメルケースのコマンドライン引数はサポートされていない。
		- 関連：[Angular13-\>14へバージョンアップ後、Jestデバッグ時に\`Error: Unknown arguments: testMatch, runInBand\`エラーが出た際の対処法 #TypeScript - Qiita](https://qiita.com/matcha_kinako/items/726c63d063a1b5ab6a3e)
- そのため、テスト単体で実行できる拡張機能がないか別の拡張機能を調査中。



## 参考にさせていただいた記事
- [Angular テストフレームワーク を jasmine/karma から Jest に移行しました - KAKEHASHI Tech Blog](https://kakehashi-dev.hatenablog.com/entry/2022/07/06/090000)
- [AngularプロジェクトへJestを導入する | lacolaco/tech](https://blog.lacolaco.net/2021/06/angular-jest-setup/)
- [Jestへの移行 · Jest](https://jestjs.io/ja/docs/migration-guide)
- [Jestの設定 · Jest](https://jestjs.io/ja/docs/configuration)
- [Jest CLI オプション · Jest](https://jestjs.io/ja/docs/cli)
- [Jestの環境設定と使いこなし術（サーバサイド向け） | DevelopersIO](https://dev.classmethod.jp/articles/jest-convenient-usage/)
- [Jest で Angular 単体テストをセットアップする方法](https://www.amadousall.com/how-to-set-up-angular-unit-testing-with-jest/)
- [How to Set Up Angular Unit Testing with Jest](https://www.amadousall.com/how-to-set-up-angular-unit-testing-with-jest/)