[Angular] AoT Compile結果を単体テスト時に利用する

@Quramy です。

さて、先日 [ng-japan](http://ngjapan.org/) というイベントに登壇し、Angularとテストについて喋らせてもらいました。

[資料はこちら](https://speakerdeck.com/quramy/ng-japan-testing-angular-app)

実は時間があまったら追加で喋ろうかな、と思っていたコンテンツがあったのですが、結果的には完全に時間オーバーしていたため、Qiitaの方に書いておきます。

## Use AOT in JIT and AOT unit testing

「AoTなのか、JiTなのかどっちだよ!」という突っ込みが聞こえてきそうですね[^4]。

Angular v4.2系から [TestBed#initTestEnvironment](https://angular.io/api/core/testing/TestBed#initTestEnvironment)にオプショナルな引数として `aotSummaries` というのが追加されました。
AoT Compile済みのNgFactoryを単体テストからも利用できるようになるという素敵野郎です。

今日(2017.06.19)時点では、殆どドキュメントも無いので、対応するcommitのリンク貼っておきますが、下記あたりです。

- [https://github.com/angular/angular/commit/547c363](https://github.com/angular/angular/commit/547c363)
- [https://github.com/angular/angular/commit/ed73d4f3ac6b542bf5ea3eb73fbe91e2ceabcdb4](https://github.com/angular/angular/commit/ed73d4f3ac6b542bf5ea3eb73fbe91e2ceabcdb4)


## どのようなメリットがあるのか

この機能を活用すると、以下のメリットがあります。

- ComponentのCompileをすっ飛ばせる
- より本番に近しいコードでテストを実行可能

特に嬉しいのが1番目の**「ComponentのCompileをすっ飛ばせる」**です。
AngularのComponent 単体テストで`ComponentFixture`を用いてテスト対象のComponentをマウントする場合、各spec毎でテスト対象ComponentのCompileが実行されます。
特にIntegration Testing Pattern[^1] を利用している場合、テスト対象のComponentでは必要のないComponentであっても、依存Moduleとしてimportsに登録していると、当該Moduleのdeclarationsに指定された全てのComponentに対してCompileが実行されます。

ComponentのCompileがテストの実行時間にどの程度効いてくるかについては、以前 [Performance Angular unit testing - Medium](https://medium.com/@Quramy/performance-angular-unit-testing-9ce30ae83e7)で測定や改善について記載しているため、詳細は省きますが、酷いと「Karma実行時間中の70%以上がCompileに要する時間だった」ということもザラです。

下図は実際にプロジェクトで使っているAngularを4.2系にupgradeした際に計測した比較です（左: AoTなし ／ 右: AoTあり）
![1fb3c956b2c0d41d51ab149c7cd92a5b.png](https://qiita-image-store.s3.amazonaws.com/0/8808/96014f61-1bed-b1d2-df09-d8c582900c86.png)

Karmaの**実行時間が9分台から2分台まで削減**されました[^2]。爆速！

## どうやって適用するの？

`@angular/cli`におけるAoTビルド(`ng build --aot`)のように、`ng test` コマンドにオプションが用意されていればよいのですが、この機能はまだ実装されていません[^3]。
まぁそのうち使えるようになるとは思うのですが、どうしてもすぐに使いたい！という人向けに雑なスクリプトを組んでみました。

```sh:test-aot.sh
#!/bin/sh

# 1. Create ngsummary.ts
cat << EOF > tsconfig.aottest.json
{
  "extends": "./tsconfig.json",
  "angularCompilerOptions": {
    "genDir": ".",
    "debug": false,
    "enableSummariesForJit": true
  }
}
EOF
./node_modules/.bin/ngc -p ./tsconfig.aottest.json

# 2. Create an entry file for AOT testing
echo "import { AppModuleNgSummary } from './app/app.module.ngsummary';" > src/test_aot.ts
sed -E "s/platformBrowserDynamicTesting\(\)/platformBrowserDynamicTesting(), () => [AppModuleNgSummary],/g" src/test.ts >> src/test_aot.ts
mv src/test.ts src/test.ts.bk && mv src/test_aot.ts src/test.ts

# 3. Run karma
ng test --single-run
CODE=$?

# 4. Cleanup temp files
mv src/test.ts.bk src/test.ts
rm tsconfig.aottest.json
node node_modules/rimraf/bin.js src/**/*.ngfactory.* src/**/*.ngsummary.* src/**/*.shim.ngstyle.ts
node node_modules/rimraf/bin.js e2e/**/*.ngfactory.* e2e/**/*.ngsummary.* e2e/**/*.shim.ngstyle.ts

exit $CODE
```

※ レポジトリは [https://github.com/Quramy/test-with-aot-summary](https://github.com/Quramy/test-with-aot-summary) にあります。

ポイントは下記あたりでしょうか:

- AoT Compile時に `enableSummariesForJit` を有効にする必要がある
- test.tsでCompile結果を読み込む必要がある

```ts:test.ts
getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting(),
  () => [AppModuleNgSummary] // こいつが重要
);
```

テスト時間が爆増して困っている！という方はお試しください。

## 参考・脚注

[^4]: 節タイトルは[Design Doc](https://docs.google.com/document/d/1VmTkz0EbEVSWfEEWEvQ5sXyQXSCvtMOw4t7pKU-jOwc)からパチっています
[^1]: [Three Ways to Test Angular Components](https://vsavkin.com/three-ways-to-test-angular-2-components-dcea8e90bd8d#cd3c)
[^2]: 実のところ、4.1.xまででも散々テスト実行時間には苦労していて、自分でCompile時間を削減する仕組みを作ったりしていました。 https://speakerdeck.com/quramy/haunted-house-of-angular-unit-testing
[^3]: [https://github.com/angular/angular-cli/issues/6650](https://github.com/angular/angular-cli/issues/6650) triageされているので、そのうち使えるようにはなるはず。