Angular6でルートパラメータに依存したComponentをテストする

# 結論
usevalueでParamMapを返すスタブを作る。
# 環境
```console
> ng -v

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 6.1.3
Node: 10.8.0
OS: win32 x64
Angular: 6.1.2
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.7.3
@angular-devkit/build-angular     0.7.3
@angular-devkit/build-optimizer   0.7.3
@angular-devkit/build-webpack     0.7.3
@angular-devkit/core              0.7.3
@angular-devkit/schematics        0.7.3
@angular/cli                      6.1.3
@ngtools/webpack                  6.1.3
@schematics/angular               0.7.3
@schematics/update                0.7.3
rxjs                              6.2.2
typescript                        2.7.2
webpack                           4.9.2
```

# テスト対象コンポーネント
[公式チュートリアル](https://angular.jp/tutorial/toh-pt5)の`HeroDetailComponent`を用いる。
ただし、今回説明に関係ない`Location`,`Input`部分は削除している。

```html:hero-detail.component.html
<div *ngIf="hero">
  <h2>{{hero.name | uppercase}} Details</h2>
  <div><span>id: </span>{{hero.id}}</div>
  <div>
    name:{{hero.name}}
  </div>
</div>
```

```typescript:hero-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import { Hero } from '../hero';
import { HeroService } from '../hero.service';

@Component({
  selector: 'app-hero-detail',
  templateUrl: './hero-detail.component.html',
  styleUrls: ['./hero-detail.component.scss']
})
export class HeroDetailComponent implements OnInit {
  hero: Hero;

  constructor(
    private route: ActivatedRoute,
    private heroService: HeroService
  ) { }

  ngOnInit(): void {
    this.getHero();
  }

  getHero(): void {
    const id = +this.route.snapshot.paramMap.get('id');
    this.heroService.getHero(id)
      .subscribe(hero => this.hero = hero);
  }
}
```

```typescript:hero-detail.component.spec.ts
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { HeroDetailComponent } from './hero-detail.component';

describe('HeroDetailComponent', () => {
  let component: HeroDetailComponent;
  let fixture: ComponentFixture<HeroDetailComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [HeroDetailComponent]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HeroDetailComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});
```

# 修正前

`hero-detail.component.spec.ts`が自動生成時のままになっているため、当然テストに失敗する。

```console
>ng test

~~~~~(中略)~~~~~

Chrome 68.0.3440 (Windows 10 0.0.0) HeroDetailComponent should create FAILED
        Error: StaticInjectorError(DynamicTestModule)[HeroDetailComponent -> ActivatedRoute]:
          StaticInjectorError(Platform: core)[HeroDetailComponent -> ActivatedRoute]:
            NullInjectorError: No provider for ActivatedRoute!
            at NullInjector.get (webpack:///./node_modules/@angular/core/fesm5/core.js?:1359:19)
            at resolveToken (webpack:///./node_modules/@angular/core/fesm5/core.js?:1597:24)
            at tryResolveToken (webpack:///./node_modules/@angular/core/fesm5/core.js?:1541:16)
            at StaticInjector.get (webpack:///./node_modules/@angular/core/fesm5/core.js?:1438:20)
            at resolveToken (webpack:///./node_modules/@angular/core/fesm5/core.js?:1597:24)
            at tryResolveToken (webpack:///./node_modules/@angular/core/fesm5/core.js?:1541:16)
            at StaticInjector.get (webpack:///./node_modules/@angular/core/fesm5/core.js?:1438:20)
            at resolveNgModuleDep (webpack:///./node_modules/@angular/core/fesm5/core.js?:8673:29)
            at NgModuleRef_.get (webpack:///./node_modules/@angular/core/fesm5/core.js?:9361:16)
            at resolveDep (webpack:///./node_modules/@angular/core/fesm5/core.js?:9726:45)
        Expected undefined to be truthy.
            at UserContext.eval (webpack:///./src/app/hero-detail/hero-detail.component.spec.ts?:20:27)
            at ZoneDelegate.invoke (webpack:///./node_modules/zone.js/dist/zone.js?:387:26)
            at ProxyZoneSpec.onInvoke (webpack:///./node_modules/zone.js/dist/zone-testing.js?:287:39)
```

# 修正箇所

```diff:hero-detail.component.spec.ts
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute, convertToParamMap } from '@angular/router';

import { HeroDetailComponent } from './hero-detail.component';

describe('HeroDetailComponent', () => {
  let component: HeroDetailComponent;
  let fixture: ComponentFixture<HeroDetailComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
-      declarations: [HeroDetailComponent]
+      declarations: [HeroDetailComponent],
+      providers: [{
+        provide: ActivatedRoute,
+        useValue: {
+          snapshot: { paramMap: convertToParamMap({ id: 11 }) }
+        }
      }]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HeroDetailComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});
```

1. `ActivatedRoute`のスタブを生成する
1. `useValue`プロパティにて、`snapshot`の戻り値を設定
  - `ParamMap`オブジェクトは`convertToParamMap`メソッドで作成すると便利

# 修正後
※既存のプロジェクトに生やしたコンポーネントなので、テスト数が違うのはご容赦ください。

```console
>ng test

~~~~~(中略)~~~~~

Chrome 68.0.3440 (Windows 10 0.0.0): Executed 11 of 11 SUCCESS (0.403 secs / 0.374 secs)
TOTAL: 11 SUCCESS
TOTAL: 11 SUCCESS
```

# 参考
- [Angular - Testing](https://angular.io/guide/testing)
- [angular - How to unit test a component that depends on parameters from ActivatedRoute?
](https://stackoverflow.com/questions/38356084/how-to-unit-test-a-component-that-depends-on-parameters-from-activatedroute)