LoginSignup
6
5

More than 5 years have passed since last update.

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

Posted at

結論

usevalueでParamMapを返すスタブを作る。

環境

> 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

テスト対象コンポーネント

公式チュートリアルHeroDetailComponentを用いる。
ただし、今回説明に関係ないLocation,Input部分は削除している。

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>
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);
  }
}
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が自動生成時のままになっているため、当然テストに失敗する。

>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)

修正箇所

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のスタブを生成する
  2. useValueプロパティにて、snapshotの戻り値を設定
    • ParamMapオブジェクトはconvertToParamMapメソッドで作成すると便利

修正後

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

>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

参考

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5