Angular コンポーネントのテスト（sample5 / ネストしたコンポーネントのテスト）

目次：[Angular コンポーネントのテスト](https://qiita.com/ringtail003/items/cdd7c5427c4836857b7e) 

---

この章では、あるコンポーネントが別のコンポーネントを所有している場合のテストの方法について説明します。

# コンポーネントの作成

まずテストを書くためのコンポーネントを作成します。

```shell
$ ng generate component sample5
```

今回はコンポーネントのクラスには手を加えません。

```TypeScript
// sample5.component.ts

import { Component, OnInit } from '@angular/core';

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

  constructor() { }

  ngOnInit() {
  }
}
```

ネストしたコンポーネントにするために、前章で作った Sample4Component を埋め込んでみます。

```diff
// sample5.component.html

- <p>
-   sample5 works!
- </p>

+ <app-sample4></app-sample4>
```

## そのままではテストが通らない

`<app-sample4></app-sample4>` を埋め込むと `'app-sample4' is not a known element:` というメッセージとともにテストが落ちるはずです。
Sample5Component のテストは Sample4Component を知らないからです。

# 解決法その１）子コンポーネントの情報をテストに教える

テスト環境でコンパイル対象となるのは declarations に宣言されたコンポーネントなので、ここに Sample4Component を追加します。

```diff
// sample5.component.spec.ts

import { Sample5Component } from './sample5.component';
+ import { Sample4Component } from '../sample4/sample4.component';

describe('Sample5Component', () => {
  ...
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        Sample5Component,
+        Sample4Component,
      ],
      ...
});
```

Sample4Component は LoggerService に依存しています。
ルートモジュールのインジェクタにサービスが登録されているような場合、Sample5Component のテストは LoggerService を見つける事ができずコンパイルが通りません。
そのようなケースではテスト環境のモジュールのインジェクタにサービス登録をする必要があります。

```diff
// sample5.component.spec.ts

import { Sample5Component } from './sample5.component';
+ import { LoggerService } from '../logger.service';

describe('Sample5Component', () => {
  ...
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      ...
+      providers: [
+        { provide: LoggerService, useValue: {} },
+      ]
    })
    ...
});
```

# 解決法その２）子コンポーネントを無視する

NO_ERRORS_SCHEMA を使用すると Angular はコンパイルできないHTML要素を無視するようになります。
Sample4Component はコンパイル対象外となるため、依存サービスを注入する必要はありません。

> **参考**   
> NO_ERRORS_SCHEMA は @experimental として定義されているため将来的に変わる可能性があります。  
> Angular v5.2.8 **ng_module.ts**  
https://github.com/angular/angular/blob/5.2.8/packages/core/src/metadata/ng_module.ts#L42-L49

```diff
// sample5.component.spec.ts

+ import { NO_ERRORS_SCHEMA } from '@angular/core';

import { Sample5Component } from './sample5.component';
import { Sample4Component } from '../sample4/sample4.component';

describe('Sample5Component', () => {
  ...
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        Sample5Component,
      ],
+      schemas: [ NO_ERRORS_SCHEMA ],
    })
    .compileComponents();
  }));
  ...
});
```

# 解決法その３）子コンポーネントの構造をテスト用に置き換える

解決法その１と同じように、コンパイル対象に Sample4Component を追加します。
依存サービスは TestBed.overrideComponent を利用して、Sample4Component のインジェクタに登録します。

```diff
// sample5.component.spec.ts

+ import { LoggerService } from '../logger.service';
import { Sample5Component } from './sample5.component';
+ import { Sample4Component } from '../sample4/sample4.component';

describe('Sample5Component', () => {
  ...
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        Sample5Component,
+        Sample4Component,
      ],
    })
+    .overrideComponent(Sample4Component, {
+      set: {
+        providers: [
+          { provide: LoggerService, useValue: {} },
+        ],
+      },
+    })
    .compileComponents();
  }));
  ...
});
```

## overrideComponent

overrideComponent はコンポーネントのメタデータ（Componentデコレータ）を上書きするメソッドです。
依存の注入以外にもさまざまなカスタマイズを加える事ができます。

### 使い方

add / remove / set のいずれかのキーを指定して、メタデータに対して行う操作を指定します。

```TypeScript
.overrideComponent(FooComponent, {
  set: { ... },
  add: { ... },
  remove: { ... },
})
```

それぞれの操作に対して、以下のようなキーと値のペアが指定できます。

```TypeScript
.overrideComponent(FooComponent, {
  set: {
    selector?: string;
    template?: string;
    templateUrl?: string;
    providers?: any[];
  },
})
```

「プロパティを表示するだけのテンプレートに置き換える」「ボタンを押したらイベントを発火させる」など、工夫次第でコンポーネント自体をテストダブルのように扱う事ができます。

```TypeScript
.overrideComponent(FooComponent, {
  set: {
    template: `
      <h1>{{value}}</h1>
      <button (click)="emit()"></button>
    `,
  }
})
```

> 公式ドキュメント **The overrideComponent method**
> https://angular.io/guide/testing#the-overridecomponent-method

---

次の記事：[sample6 / 入出力を伴うコンポーネントのテスト](https://qiita.com/ringtail003/items/6c4d527b1ec3558a8c7f)