Angular8でjestを利用できるようにする

# Angular8でjestを利用できるようにする

Angular8でjestを利用できるようにする記事はいくつかあるが、それだけでは利用できなかったので調査してみた。

`Can't resolve all parameters for Tc1Component: (?).`というエラーが発生していました。
これ自体は、Testbedでcomponentのinjectionが解決できず、インスタンスを生成できず失敗するという現象でした。

## サンプルプロジェクト

[Github: Angular8-Jest](https://github.com/AkasakiTomohiro/Angular8-Jest)

## 環境構築

### Angularプロジェクト作成

まずはAngularのプロジェクトを作成する`ng new TestProject`
そうすると次のようなディレクトリ構成が生成される。
![スクリーンショット 2019-11-29 23.12.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152941/5f9c5925-1a31-c750-a810-c95a341d30e9.png)

このサンプルプロジェクトを利用しやすくするために次を削除します
1. src/
2. e2e/
3. tsconfig.app.json
4. tsconfig.spec.json
5. karma.conf.js

`rm -rf ./e2e/ ./src/ ./tsconfig.app.json ./tsconfig.spec.json karma.conf.js`

ここで、`Angular.json`からプロジェクト情報を削除したところですが、`ng g application`コマンドは最低でも１つのプロジェクトが`Angular.json`に登録されていないと実行できないので、先にプロジェクトを作成します。
今回は`pj1`という名前にしたいので、`ng g application pj1`と入力します。

pj1プロジェクトを作成したので、`angular.json`の`projects`に登録されている`TestProject`を削除します。

これでAngular８の初期プロジェクトの作成が完了しました。

### 検証用にComponentとServiceを追加する

`ng g component tc1`と`ng g service ts1`で作成する。
また、上記の`Can't resolve all parameters for Tc1Component: (?).`をjest導入後に出せるようにするために次の設定を`tc1.component.ts`に行う。

``` projects/pj1/src/app/tc1/tc1.component.ts
import { Component, OnInit } from '@angular/core';
import { Ts1Service } from '../ts1.service';

@Component({
  selector: 'app-tc1',
  templateUrl: './tc1.component.html',
  styleUrls: ['./tc1.component.css']
})
export class Tc1Component implements OnInit {

  constructor(private ts1: Ts1Service) { }

  ngOnInit() {
  }

}
```

`ng test`で正しくテストできることを確認する

![スクリーンショット 2019-11-30 00.05.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152941/d34dcbaa-bfa9-d25c-1c3a-2b3c2980028b.png)


### 単体テストフレームワークをjasmineからjestに変更する

・不要なライブラリ群とkarma用のファイルを削除する。
`npm r @types/jasmine @types/jasminewd2 jasmine-core jasmine-spec-reporter karma karma-chrome-launcher karma-coverage-istanbul-reporter karma-jasmine karma-jasmine-html-reporter`
`rm -f ./projects/pj1/karma.conf.js ./projects/pj1/src/test.ts`

・jest関係のライブラリをインストールする
`npm i -D jest @types/jest @angular-builders/jest`

2019/11/30 追記：
これらの設定は`jest-preset-angular`が`7.1.1`でしか動作できないことを確認しました。
次のコマンドも合わせて実行してください。
`npm i -D jest-preset-angular@7.1.1`

・Typescriptのファイルを修正する。

`tsconfig.json`の`compilerOptions`に`"esModuleInterop": true`と`"emitDecoratorMetadata": true`を追加する

ついでに私は、`"emitDecoratorMetadata": true`の設定をしていなかったために、`Can't resolve all parameters for Tc1Component: (?).`のエラーが出ていました。

``` tsconfig.json
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "module": "esnext",
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2015",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ],
    "esModuleInterop": true,
    "emitDecoratorMetadata": true
  },
  "angularCompilerOptions": {
    "fullTemplateTypeCheck": true,
    "strictInjectionParameters": true
  }
}
```

`tsconfig.app.json`と`tsconfig.spec.json`は`exclude`から `"src/test.ts"`を削除する
`tsconfig.spec.json`は`types`の`jasmine`を`jest`に変更する

``` projects/pj1/tsconfig.app.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "../../out-tsc/app",
    "types": []
  },
  "files": [
    "src/main.ts",
    "src/polyfills.ts"
  ],
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "src/**/*.spec.ts"
  ]
}
```

``` projects/pj1/tsconfig.spec.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "../../out-tsc/spec",
    "types": [
      "jest",
      "node"
    ]
  },
  "files": [
    "src/polyfills.ts"
  ],
  "include": [
    "src/**/*.spec.ts",
    "src/**/*.d.ts"
  ]
}
```

・jestの設定を行う

jestの設定ファイルを作成する

``` projects/pj1/jest.config.js
module.exports = {
  globals: {
    'ts-jest': {
      tsConfig: '<rootDir>/projects/pj1/tsconfig.spec.json',
      stringifyContentPathRegex: '\\.html$',
      astTransformers: [
        'jest-preset-angular/InlineHtmlStripStylesTransformer',
      ],
    },
  },
  testMatch: ['**/*.spec.ts'],
  transform: {
    '^.+\\.ts$': 'ts-jest',
  },
  testEnvironment: 'jest-environment-jsdom-thirteen',
  moduleFileExtensions: ['ts', 'html', 'js', 'json'],
  moduleNameMapper: {
    '^src/(.*)$': '<rootDir>/src/$1',
    '^app/(.*)$': '<rootDir>/src/app/$1',
    '^assets/(.*)$': '<rootDir>/src/assets/$1',
    '^environments/(.*)$': '<rootDir>/src/environments/$1',
  },
  transformIgnorePatterns: ['node_modules/.*'],
  snapshotSerializers: [
    'jest-preset-angular/AngularSnapshotSerializer.js',
    'jest-preset-angular/HTMLCommentSerializer.js',
  ],
};
```

※以下の箇所のみ、プロジェクトのディレクトリ情報を含むので、作成したプロジェクトパスになるように適宜調整してください。
`tsConfig: '<rootDir>/projects/pj1/tsconfig.spec.json'`

・Angularの設定を行う

このままでは`ng test`で単体テストを行うことができないので、`angular.json`で設定していく。
※必要な部分だけを表示しているので、該当箇所のみを変更するようにしてください。

以下のデータが`ng test`したときに利用される設定になります。

``` angular.json
{
  "projects": {
    "pj1": {
      "architect": {
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "projects/pj1/src/test.ts",
            "polyfills": "projects/pj1/src/polyfills.ts",
            "tsConfig": "projects/pj1/tsconfig.spec.json",
            "karmaConfig": "projects/pj1/karma.conf.js",
            "assets": [
              "projects/pj1/src/favicon.ico",
              "projects/pj1/src/assets"
            ],
            "styles": [
              "projects/pj1/src/styles.scss"
            ],
            "scripts": []
          }
        }
      }
    }
  }
}
```

これを次のように修正を行ってください。

``` angular.json
{
  "projects": {
    "pj1": {
      "architect": {
        "test": {
          "builder": "@angular-builders/jest:run"
        }
      }
    }
  }
}
```

### ng testで単体テストを実行してみる

まずは、実際にテストが動作することを確認してみましょう。
次のように５つのテストが通っていれば成功です。

![スクリーンショット 2019-11-30 00.09.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152941/4e7e075b-919d-a946-e967-3e9a2417e26f.png)

## おまけ

実際に`Can't resolve all parameters for Tc1Component: (?).`のエラーを出してみるとしましょう。
`tsconfig.json`から`"emitDecoratorMetadata": true`の設定を削除して`ng test`を実行してみてください。

``` tsconfig.json
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "module": "esnext",
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2015",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ],
    "esModuleInterop": true
  },
  "angularCompilerOptions": {
    "fullTemplateTypeCheck": true,
    "strictInjectionParameters": true
  }
}
```

![スクリーンショット 2019-11-30 00.13.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152941/b8d953f1-f9c9-d6e2-fa85-415e759c32c5.png)

## 最後に

私は、tsconfigの設定が悪いことに気づくまで、2時間も要してしました。
少しでも、皆様の時間がこの記事を通して短縮できること願っています。

では、よきjestライフを。