詳細ガイド
テスト

サービスのテスト

サービスが意図通りに動作していることを確認するには、サービス専用のテストを作成できます。

サービスは、多くの場合、ユニットテストを実行するのに最もスムーズなファイルです。 以下は、Angularテストユーティリティの助けを借りずに記述された ValueService の同期および非同期のユニットテストです。

app/demo/demo.spec.ts

      
import {LightswitchComponent, MasterService, ValueService, ReversePipe} from './demo';///////// Fakes /////////export class FakeValueService extends ValueService {  override value = 'faked service value';}////////////////////////describe('demo (no TestBed):', () => {  // Straight Jasmine testing without Angular's testing support  describe('ValueService', () => {    let service: ValueService;    beforeEach(() => {      service = new ValueService();    });    it('#getValue should return real value', () => {      expect(service.getValue()).toBe('real value');    });    it('#getObservableValue should return value from observable', (done: DoneFn) => {      service.getObservableValue().subscribe((value) => {        expect(value).toBe('observable value');        done();      });    });    it('#getPromiseValue should return value from a promise', (done: DoneFn) => {      service.getPromiseValue().then((value) => {        expect(value).toBe('promise value');        done();      });    });  });  // MasterService requires injection of a ValueService  describe('MasterService without Angular testing support', () => {    let masterService: MasterService;    it('#getValue should return real value from the real service', () => {      masterService = new MasterService(new ValueService());      expect(masterService.getValue()).toBe('real value');    });    it('#getValue should return faked value from a fakeService', () => {      masterService = new MasterService(new FakeValueService());      expect(masterService.getValue()).toBe('faked service value');    });    it('#getValue should return faked value from a fake object', () => {      const fake = {getValue: () => 'fake value'};      masterService = new MasterService(fake as ValueService);      expect(masterService.getValue()).toBe('fake value');    });    it('#getValue should return stubbed value from a spy', () => {      // create `getValue` spy on an object representing the ValueService      const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);      // set the value to return when the `getValue` spy is called.      const stubValue = 'stub value';      valueServiceSpy.getValue.and.returnValue(stubValue);      masterService = new MasterService(valueServiceSpy);      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });  });  describe('MasterService (no beforeEach)', () => {    it('#getValue should return stubbed value from a spy', () => {      const {masterService, stubValue, valueServiceSpy} = setup();      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });    function setup() {      const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);      const stubValue = 'stub value';      const masterService = new MasterService(valueServiceSpy);      valueServiceSpy.getValue.and.returnValue(stubValue);      return {masterService, stubValue, valueServiceSpy};    }  });  describe('ReversePipe', () => {    let pipe: ReversePipe;    beforeEach(() => {      pipe = new ReversePipe();    });    it('transforms "abc" to "cba"', () => {      expect(pipe.transform('abc')).toBe('cba');    });    it('no change to palindrome: "able was I ere I saw elba"', () => {      const palindrome = 'able was I ere I saw elba';      expect(pipe.transform(palindrome)).toBe(palindrome);    });  });  describe('LightswitchComp', () => {    it('#clicked() should toggle #isOn', () => {      const comp = new LightswitchComponent();      expect(comp.isOn).withContext('off at first').toBe(false);      comp.clicked();      expect(comp.isOn).withContext('on after click').toBe(true);      comp.clicked();      expect(comp.isOn).withContext('off after second click').toBe(false);    });    it('#clicked() should set #message to "is on"', () => {      const comp = new LightswitchComponent();      expect(comp.message)        .withContext('off at first')        .toMatch(/is off/i);      comp.clicked();      expect(comp.message).withContext('on after clicked').toMatch(/is on/i);    });  });});

依存関係のあるサービス

サービスは、Angular がコンストラクターに注入する他のサービスに依存することがよくあります。 多くの場合、サービスのコンストラクターを呼び出す際に、これらの依存関係を手動で作成して 注入 できます。

MasterService は、単純な例です。

app/demo/demo.ts

      
import {  Component,  ContentChildren,  Directive,  EventEmitter,  HostBinding,  HostListener,  inject,  Injectable,  input,  output,  OnChanges,  OnDestroy,  OnInit,  Pipe,  PipeTransform,  SimpleChanges,  signal,} from '@angular/core';import {FormsModule} from '@angular/forms';import {of} from 'rxjs';import {delay} from 'rxjs/operators';import {sharedImports} from '../shared/shared';////////// The App: Services and Components for the tests. //////////////export interface Hero {  name: string;}////////// Services ///////////////@Injectable()export class ValueService {  value = 'real value';  getValue() {    return this.value;  }  setValue(value: string) {    this.value = value;  }  getObservableValue() {    return of('observable value');  }  getPromiseValue() {    return Promise.resolve('promise value');  }  getObservableDelayValue() {    return of('observable delay value').pipe(delay(10));  }}@Injectable()export class MasterService {  public valueService = inject(ValueService);  getValue() {    return this.valueService.getValue();  }}/////////// Pipe /////////////////* * Reverse the input string. */@Pipe({name: 'reverse'})export class ReversePipe implements PipeTransform {  transform(s: string) {    let r = '';    for (let i = s.length; i; ) {      r += s[--i];    }    return r;  }}//////////// Components /////////////@Component({  selector: 'bank-account',  template: ` Bank Name: {{ bank() }} Account Id: {{ id() }} `,})export class BankAccountComponent {  bank = input('');  id = input('', {alias: 'account'});}/** A component with attributes, styles, classes, and property setting */@Component({  selector: 'bank-account-parent',  template: `    <bank-account      bank="RBC"      account="4747"      [style.width.px]="width"      [style.color]="color"      [class.closed]="isClosed"      [class.open]="!isClosed"    />  `,  imports: [BankAccountComponent],})export class BankAccountParentComponent {  width = 200;  color = 'red';  isClosed = true;}@Component({  selector: 'lightswitch-comp',  template: ` <button type="button" (click)="clicked()">Click me!</button>    <span>{{ message }}</span>`,})export class LightswitchComponent {  isOn = false;  clicked() {    this.isOn = !this.isOn;  }  get message() {    return `The light is ${this.isOn ? 'On' : 'Off'}`;  }}@Component({  selector: 'child-1',  template: '<span>Child-1({{text()}})</span>',})export class Child1Component {  text = input('Original');}@Component({  selector: 'child-2',  template: '<div>Child-2({{text()}})</div>',})export class Child2Component {  text = input('');}@Component({  selector: 'child-3',  template: '<div>Child-3({{text}})</div>',})export class Child3Component {  text = input('');}@Component({  selector: 'input-comp',  template: '<input [(ngModel)]="name">',  imports: [FormsModule],})export class InputComponent {  name = signal('John');}@Component({  selector: 'input-value-comp',  template: ` Name: <input [value]="name" /> {{ name }} `,})export class InputValueBinderComponent {  name = 'Sally'; // initial value}@Component({  selector: 'parent-comp',  imports: [Child1Component],  template: 'Parent(<child-1></child-1>)',})export class ParentComponent {}@Component({  selector: 'io-comp',  template:    '<button type="button" class="hero" (click)="click()">Original {{hero().name}}</button>',})export class IoComponent {  hero = input.required<Hero>();  selected = output<Hero>();  click() {    this.selected.emit(this.hero);  }}@Component({  selector: 'io-parent-comp',  template: `    @if (!selectedHero) {      <p><i>Click to select a hero</i></p>    }    @if (selectedHero) {      <p>The selected hero is {{ selectedHero.name }}</p>    }    @for (hero of heroes; track hero) {      <io-comp [hero]="hero" (selected)="onSelect($event)"> </io-comp>    }  `,  imports: [IoComponent, sharedImports],})export class IoParentComponent {  heroes: Hero[] = [{name: 'Bob'}, {name: 'Carol'}, {name: 'Ted'}, {name: 'Alice'}];  selectedHero!: Hero;  onSelect(hero: Hero) {    this.selectedHero = hero;  }}@Component({  selector: 'my-if-comp',  template: 'MyIf(@if (showMore) {<span>More</span>})',  imports: [sharedImports],})export class MyIfComponent {  showMore = false;}@Component({  selector: 'my-service-comp',  template: 'injected value: {{valueService.value}}',  providers: [ValueService],})export class TestProvidersComponent {  public valueService = inject(ValueService);}@Component({  selector: 'my-service-comp',  template: 'injected value: {{valueService.value}}',  viewProviders: [ValueService],})export class TestViewProvidersComponent {  public valueService = inject(ValueService);}@Component({  selector: 'external-template-comp',  templateUrl: './demo-external-template.html',})export class ExternalTemplateComponent {  private service = inject(ValueService, {optional: true});  serviceValue = this.service?.getValue() ?? '';}@Component({  selector: 'comp-w-ext-comp',  imports: [ExternalTemplateComponent],  template: `    <h3>comp-w-ext-comp</h3>    <external-template-comp></external-template-comp>  `,})export class InnerCompWithExternalTemplateComponent {}@Component({selector: 'needs-content', template: '<ng-content></ng-content>'})export class NeedsContentComponent {  // children with #content local variable  @ContentChildren('content') children: any;}@Component({  selector: 'reverse-pipe-comp',  template: `    <input [(ngModel)]="text" />    <span>{{ text | reverse }}</span>  `,  imports: [ReversePipe, FormsModule],})export class ReversePipeComponent {  text = 'my dog has fleas.';}@Component({  imports: [NeedsContentComponent],  template: '<div>Replace Me</div>',})export class ShellComponent {}@Component({  selector: 'demo-comp',  template: `    <h1>Specs Demo</h1>    <my-if-parent-comp></my-if-parent-comp>    <hr>    <h3>Input/Output Component</h3>    <io-parent-comp></io-parent-comp>    <hr>    <h3>External Template Component</h3>    <external-template-comp></external-template-comp>    <hr>    <h3>Component With External Template Component</h3>    <comp-w-ext-comp></comp-w-ext-comp>    <hr>    <h3>Reverse Pipe</h3>    <reverse-pipe-comp></reverse-pipe-comp>    <hr>    <h3>InputValueBinder Directive</h3>    <input-value-comp></input-value-comp>    <hr>    <h3>Button Component</h3>    <lightswitch-comp></lightswitch-comp>    <hr>    <h3>Needs Content</h3>    <needs-content #nc>      <child-1 #content text="My"></child-1>      <child-2 #content text="dog"></child-2>      <child-2 text="has"></child-2>      <child-3 #content text="fleas"></child-3>      <div #content>!</div>    </needs-content>  `,  imports: [    Child1Component,    Child2Component,    Child3Component,    ExternalTemplateComponent,    InnerCompWithExternalTemplateComponent,    InputValueBinderComponent,    IoParentComponent,    LightswitchComponent,    NeedsContentComponent,    ReversePipeComponent,  ],})export class DemoComponent {}//////// Aggregations ////////////export const demoProviders = [MasterService, ValueService];

MasterService は、唯一のメソッドである getValue を、注入された ValueService に委譲します。

テストを行うには、いくつかの方法があります。

app/demo/demo.spec.ts

      
import {LightswitchComponent, MasterService, ValueService, ReversePipe} from './demo';///////// Fakes /////////export class FakeValueService extends ValueService {  override value = 'faked service value';}////////////////////////describe('demo (no TestBed):', () => {  // Straight Jasmine testing without Angular's testing support  describe('ValueService', () => {    let service: ValueService;    beforeEach(() => {      service = new ValueService();    });    it('#getValue should return real value', () => {      expect(service.getValue()).toBe('real value');    });    it('#getObservableValue should return value from observable', (done: DoneFn) => {      service.getObservableValue().subscribe((value) => {        expect(value).toBe('observable value');        done();      });    });    it('#getPromiseValue should return value from a promise', (done: DoneFn) => {      service.getPromiseValue().then((value) => {        expect(value).toBe('promise value');        done();      });    });  });  // MasterService requires injection of a ValueService  describe('MasterService without Angular testing support', () => {    let masterService: MasterService;    it('#getValue should return real value from the real service', () => {      masterService = new MasterService(new ValueService());      expect(masterService.getValue()).toBe('real value');    });    it('#getValue should return faked value from a fakeService', () => {      masterService = new MasterService(new FakeValueService());      expect(masterService.getValue()).toBe('faked service value');    });    it('#getValue should return faked value from a fake object', () => {      const fake = {getValue: () => 'fake value'};      masterService = new MasterService(fake as ValueService);      expect(masterService.getValue()).toBe('fake value');    });    it('#getValue should return stubbed value from a spy', () => {      // create `getValue` spy on an object representing the ValueService      const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);      // set the value to return when the `getValue` spy is called.      const stubValue = 'stub value';      valueServiceSpy.getValue.and.returnValue(stubValue);      masterService = new MasterService(valueServiceSpy);      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });  });  describe('MasterService (no beforeEach)', () => {    it('#getValue should return stubbed value from a spy', () => {      const {masterService, stubValue, valueServiceSpy} = setup();      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });    function setup() {      const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);      const stubValue = 'stub value';      const masterService = new MasterService(valueServiceSpy);      valueServiceSpy.getValue.and.returnValue(stubValue);      return {masterService, stubValue, valueServiceSpy};    }  });  describe('ReversePipe', () => {    let pipe: ReversePipe;    beforeEach(() => {      pipe = new ReversePipe();    });    it('transforms "abc" to "cba"', () => {      expect(pipe.transform('abc')).toBe('cba');    });    it('no change to palindrome: "able was I ere I saw elba"', () => {      const palindrome = 'able was I ere I saw elba';      expect(pipe.transform(palindrome)).toBe(palindrome);    });  });  describe('LightswitchComp', () => {    it('#clicked() should toggle #isOn', () => {      const comp = new LightswitchComponent();      expect(comp.isOn).withContext('off at first').toBe(false);      comp.clicked();      expect(comp.isOn).withContext('on after click').toBe(true);      comp.clicked();      expect(comp.isOn).withContext('off after second click').toBe(false);    });    it('#clicked() should set #message to "is on"', () => {      const comp = new LightswitchComponent();      expect(comp.message)        .withContext('off at first')        .toMatch(/is off/i);      comp.clicked();      expect(comp.message).withContext('on after clicked').toMatch(/is on/i);    });  });});

最初のテストでは、newValueService を作成し、MasterService コンストラクターに渡します。

ただし、実際のサービスを注入することは、ほとんどの場合、うまく機能しません。ほとんどの依存サービスは作成と制御が難しいからです。

代わりに、依存関係をモック化し、ダミー値を使用するか、関連するサービスメソッドに スパイ を作成します。

HELPFUL: スパイは、通常、サービスをモック化する最良の方法なので、スパイを使用することをお勧めします。

これらの標準的なテストテクニックは、サービスを単体でユニットテストするのに適しています。

ただし、ほとんどの場合、Angular の依存関係注入を使用してサービスをアプリケーションクラスに注入します。そのため、その使用パターンを反映したテストを行う必要があります。 Angular のテストユーティリティを使用すると、注入されたサービスがどのように動作するかを簡単に調査できます。

TestBed を使用したサービスのテスト

アプリケーションは、Angular の 依存関係注入 (DI) に依存してサービスを作成します。 サービスが依存サービスを持っている場合、DI はその依存サービスを見つけたり、作成します。 さらに、その依存サービスに独自の依存関係がある場合、DI はそれらも探し出して作成します。

サービスの 消費者 として、あなたはこれらについて心配する必要はありません。 コンストラクター引数の順序や、それらがどのように作成されるかについて心配する必要はありません。

サービスの テスター として、少なくともサービス依存関係の最初のレベルについて考える必要はありますが、TestBed テストユーティリティを使用してサービスを提供して作成し、コンストラクター引数の順序を処理するときは、Angular DI にサービスの作成を任せることができます。

Angular TestBed

TestBed は、Angular のテストユーティリティの中で最も重要なものです。 TestBed は、Angular の @NgModule をエミュレートする、動的に構築された Angular の テスト モジュールを作成します。

TestBed.configureTestingModule() メソッドは、@NgModule のほとんどのプロパティを持つことができるメタデータオブジェクトを受け取ります。

サービスをテストするには、テストまたはモックするサービスの配列を providers メタデータプロパティに設定します。

app/demo/demo.testbed.spec.ts (beforeEach で ValueService を提供)

      
import {Component, DebugElement, Injectable} from '@angular/core';import {  ComponentFixture,  fakeAsync,  inject,  TestBed,  tick,  waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import {  BankAccountComponent,  BankAccountParentComponent,  Child1Component,  Child2Component,  Child3Component,  ExternalTemplateComponent,  InputComponent,  IoComponent,  IoParentComponent,  LightswitchComponent,  MasterService,  MyIfChildComponent,  MyIfComponent,  MyIfParentComponent,  NeedsContentComponent,  ParentComponent,  ReversePipeComponent,  ShellComponent,  TestProvidersComponent,  TestViewProvidersComponent,  ValueService,} from './demo';export class NotProvided extends ValueService {  /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => {  ////////  Service Tests  /////////////  describe('ValueService', () => {    let service: ValueService;    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});      service = TestBed.inject(ValueService);    });    it('should use ValueService', () => {      service = TestBed.inject(ValueService);      expect(service.getValue()).toBe('real value');    });    it('can inject a default value when service is not provided', () => {      expect(TestBed.inject(NotProvided, null)).toBeNull();    });    it('test should wait for ValueService.getPromiseValue', waitForAsync(() => {      service.getPromiseValue().then((value) => expect(value).toBe('promise value'));    }));    it('test should wait for ValueService.getObservableValue', waitForAsync(() => {      service.getObservableValue().subscribe((value) => expect(value).toBe('observable value'));    }));    // Must use done. See https://github.com/angular/angular/issues/10127    it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => {      service.getObservableDelayValue().subscribe((value) => {        expect(value).toBe('observable delay value');        done();      });    });    it('should allow the use of fakeAsync', fakeAsync(() => {      let value: any;      service.getPromiseValue().then((val: any) => (value = val));      tick(); // Trigger JS engine cycle until all promises resolve.      expect(value).toBe('promise value');    }));  });  describe('MasterService', () => {    let masterService: MasterService;    let valueServiceSpy: jasmine.SpyObj<ValueService>;    beforeEach(() => {      const spy = jasmine.createSpyObj('ValueService', ['getValue']);      TestBed.configureTestingModule({        // Provide both the service-to-test and its (spy) dependency        providers: [MasterService, {provide: ValueService, useValue: spy}],      });      // Inject both the service-to-test and its (spy) dependency      masterService = TestBed.inject(MasterService);      valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>;    });    it('#getValue should return stubbed value from a spy', () => {      const stubValue = 'stub value';      valueServiceSpy.getValue.and.returnValue(stubValue);      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });  });  describe('use inject within `it`', () => {    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});    });    it('should use modified providers', inject([ValueService], (service: ValueService) => {      service.setValue('value modified in beforeEach');      expect(service.getValue()).toBe('value modified in beforeEach');    }));  });  describe('using waitForAsync(inject) within beforeEach', () => {    let serviceValue: string;    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});    });    beforeEach(waitForAsync(      inject([ValueService], (service: ValueService) => {        service.getPromiseValue().then((value) => (serviceValue = value));      }),    ));    it('should use asynchronously modified value ... in synchronous test', () => {      expect(serviceValue).toBe('promise value');    });  });  /////////// Component Tests //////////////////  describe('TestBed component tests', () => {    // beforeEach(waitForAsync(() => {    //   TestBed.configureTestingModule()    //     // Compile everything in DemoModule    //     ;    // }));    it('should create a component with inline template', () => {      const fixture = TestBed.createComponent(Child1Component);      fixture.detectChanges();      expect(fixture).toHaveText('Child');    });    it('should create a component with external template', () => {      const fixture = TestBed.createComponent(ExternalTemplateComponent);      fixture.detectChanges();      expect(fixture).toHaveText('from external template');    });    it('should allow changing members of the component', () => {      const fixture = TestBed.createComponent(MyIfComponent);      fixture.detectChanges();      expect(fixture).toHaveText('MyIf()');      fixture.componentInstance.showMore = true;      fixture.detectChanges();      expect(fixture).toHaveText('MyIf(More)');    });    it('should create a nested component bound to inputs/outputs', () => {      const fixture = TestBed.createComponent(IoParentComponent);      fixture.detectChanges();      const heroes = fixture.debugElement.queryAll(By.css('.hero'));      expect(heroes.length).withContext('has heroes').toBeGreaterThan(0);      const comp = fixture.componentInstance;      const hero = comp.heroes[0];      click(heroes[0]);      fixture.detectChanges();      const selected = fixture.debugElement.query(By.css('p'));      expect(selected).toHaveText(hero.name);    });    it('can access the instance variable of an `*ngFor` row component', () => {      const fixture = TestBed.createComponent(IoParentComponent);      const comp = fixture.componentInstance;      const heroName = comp.heroes[0].name; // first hero's name      fixture.detectChanges();      const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow      const hero = ngForRow.context.hero; // the hero object passed into the row      expect(hero.name).withContext('ngRow.context.hero').toBe(heroName);      const rowComp = ngForRow.componentInstance;      // jasmine.any is an "instance-of-type" test.      expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent));      expect(rowComp.hero.name).withContext('component.hero').toBe(heroName);    });    it('should support clicking a button', () => {      const fixture = TestBed.createComponent(LightswitchComponent);      const btn = fixture.debugElement.query(By.css('button'));      const span = fixture.debugElement.query(By.css('span')).nativeElement;      fixture.detectChanges();      expect(span.textContent)        .withContext('before click')        .toMatch(/is off/i);      click(btn);      fixture.detectChanges();      expect(span.textContent).withContext('after click').toMatch(/is on/i);    });    // ngModel is async so we must wait for it with promise-based `whenStable`    it('should support entering text in input box (ngModel)', waitForAsync(() => {      const expectedOrigName = 'John';      const expectedNewName = 'Sally';      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      expect(comp.name)        .withContext(`At start name should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // wait until ngModel binds comp.name to input box      fixture        .whenStable()        .then(() => {          expect(input.value)            .withContext(              `After ngModel updates input box, input.value should be ${expectedOrigName} `,            )            .toBe(expectedOrigName);          // simulate user entering new name in input          input.value = expectedNewName;          // that change doesn't flow to the component immediately          expect(comp.name())            .withContext(              `comp.name should still be ${expectedOrigName} after value change, before binding happens`,            )            .toBe(expectedOrigName);          // Dispatch a DOM event so that Angular learns of input value change.          // then wait while ngModel pushes input.box value to comp.name          input.dispatchEvent(new Event('input'));          return fixture.whenStable();        })        .then(() => {          expect(comp.name())            .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `)            .toBe(expectedNewName);        });    }));    // fakeAsync version of ngModel input test enables sync test style    // synchronous `tick` replaces asynchronous promise-base `whenStable`    it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => {      const expectedOrigName = 'John';      const expectedNewName = 'Sally';      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      expect(comp.name)        .withContext(`At start name should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // wait until ngModel binds comp.name to input box      tick();      expect(input.value)        .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // simulate user entering new name in input      input.value = expectedNewName;      // that change doesn't flow to the component immediately      expect(comp.name)        .withContext(          `comp.name should still be ${expectedOrigName} after value change, before binding happens`,        )        .toBe(expectedOrigName);      // Dispatch a DOM event so that Angular learns of input value change.      // then wait a tick while ngModel pushes input.box value to comp.name      input.dispatchEvent(new Event('input'));      tick();      expect(comp.name)        .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `)        .toBe(expectedNewName);    }));    it('ReversePipeComp should reverse the input text', fakeAsync(() => {      const inputText = 'the quick brown fox.';      const expectedText = '.xof nworb kciuq eht';      const fixture = TestBed.createComponent(ReversePipeComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement;      // simulate user entering new name in input      input.value = inputText;      // Dispatch a DOM event so that Angular learns of input value change.      // then wait a tick while ngModel pushes input.box value to comp.text      // and Angular updates the output span      input.dispatchEvent(new Event('input'));      tick();      fixture.detectChanges();      expect(span.textContent).withContext('output span').toBe(expectedText);      expect(comp.text).withContext('component.text').toBe(inputText);    }));    // Use this technique to find attached directives of any kind    it('can examine attached directives and listeners', () => {      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const inputEl = fixture.debugElement.query(By.css('input'));      expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel);      const ngControl = inputEl.injector.get(NgControl);      expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl));      expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2);    });    it('BankAccountComponent should set attributes, styles, classes, and properties', () => {      const fixture = TestBed.createComponent(BankAccountParentComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      // the only child is debugElement of the BankAccount component      const el = fixture.debugElement.children[0];      const childComp = el.componentInstance as BankAccountComponent;      expect(childComp).toEqual(jasmine.any(BankAccountComponent));      expect(el.context).withContext('context is the child component').toBe(childComp);      expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id);      expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank);      expect(el.classes['closed']).withContext('closed class').toBe(true);      expect(el.classes['open']).withContext('open class').toBeFalsy();      expect(el.styles['color']).withContext('color style').toBe(comp.color);      expect(el.styles['width'])        .withContext('width style')        .toBe(comp.width + 'px');      // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future?      // expect(el.properties['customProperty']).toBe(true, 'customProperty');    });  });  describe('TestBed component overrides:', () => {    it("should override ChildComp's template", () => {      const fixture = TestBed.configureTestingModule({        imports: [Child1Component],      })        .overrideComponent(Child1Component, {          set: {template: '<span>Fake</span>'},        })        .createComponent(Child1Component);      fixture.detectChanges();      expect(fixture).toHaveText('Fake');    });    it("should override TestProvidersComp's ValueService provider", () => {      const fixture = TestBed.configureTestingModule({        imports: [TestProvidersComponent],      })        .overrideComponent(TestProvidersComponent, {          remove: {providers: [ValueService]},          add: {providers: [{provide: ValueService, useClass: FakeValueService}]},          // Or replace them all (this component has only one provider)          // set:    { providers: [{ provide: ValueService, useClass: FakeValueService }] },        })        .createComponent(TestProvidersComponent);      fixture.detectChanges();      expect(fixture).toHaveText('injected value: faked value', 'text');      // Explore the providerTokens      const tokens = fixture.debugElement.providerTokens;      expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor);      expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent);      expect(tokens).withContext('ValueService').toContain(ValueService);    });    it("should override TestViewProvidersComp's ValueService viewProvider", () => {      const fixture = TestBed.configureTestingModule({        imports: [TestViewProvidersComponent],      })        .overrideComponent(TestViewProvidersComponent, {          // remove: { viewProviders: [ValueService]},          // add:    { viewProviders: [{ provide: ValueService, useClass: FakeValueService }]          // },          // Or replace them all (this component has only one viewProvider)          set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]},        })        .createComponent(TestViewProvidersComponent);      fixture.detectChanges();      expect(fixture).toHaveText('injected value: faked value');    });    it("injected provider should not be same as component's provider", () => {      // TestComponent is parent of TestProvidersComponent      @Component({        template: '<my-service-comp></my-service-comp>',        imports: [TestProvidersComponent],      })      class TestComponent {}      // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent      const fixture = TestBed.configureTestingModule({        imports: [TestComponent, TestProvidersComponent],        providers: [ValueService],      })        .overrideComponent(TestComponent, {          set: {providers: [{provide: ValueService, useValue: {}}]},        })        .overrideComponent(TestProvidersComponent, {          set: {providers: [{provide: ValueService, useClass: FakeValueService}]},        })        .createComponent(TestComponent);      let testBedProvider!: ValueService;      // `inject` uses TestBed's injector      inject([ValueService], (s: ValueService) => (testBedProvider = s))();      const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService;      const tpcProvider = fixture.debugElement.children[0].injector.get(        ValueService,      ) as FakeValueService;      expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider);      expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider);      expect(testBedProvider instanceof ValueService)        .withContext('testBedProvider is ValueService')        .toBe(true);      expect(tcProvider)        .withContext('tcProvider is {}')        .toEqual({} as ValueService);      expect(tpcProvider instanceof FakeValueService)        .withContext('tpcProvider is FakeValueService')        .toBe(true);    });    it('can access template local variables as references', () => {      const fixture = TestBed.configureTestingModule({        imports: [          ShellComponent,          NeedsContentComponent,          Child1Component,          Child2Component,          Child3Component,        ],      })        .overrideComponent(ShellComponent, {          set: {            selector: 'test-shell',            imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component],            template: `          <needs-content #nc>            <child-1 #content text="My"></child-1>            <child-2 #content text="dog"></child-2>            <child-2 text="has"></child-2>            <child-3 #content text="fleas"></child-3>            <div #content>!</div>          </needs-content>          `,          },        })        .createComponent(ShellComponent);      fixture.detectChanges();      // NeedsContentComp is the child of ShellComp      const el = fixture.debugElement.children[0];      const comp = el.componentInstance;      expect(comp.children.toArray().length)        .withContext('three different child components and an ElementRef with #content')        .toBe(4);      expect(el.references['nc']).withContext('#nc reference to component').toBe(comp);      // Filter for DebugElements with a #content reference      const contentRefs = el.queryAll((de) => de.references['content']);      expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4);    });  });  describe('nested (one-deep) component override', () => {    beforeEach(() => {      TestBed.configureTestingModule({        imports: [ParentComponent, FakeChildComponent],      }).overrideComponent(ParentComponent, {        set: {imports: [FakeChildComponent]},      });    });    it('ParentComp should use Fake Child component', () => {      const fixture = TestBed.createComponent(ParentComponent);      fixture.detectChanges();      expect(fixture).toHaveText('Parent(Fake Child)');    });  });  describe('nested (two-deep) component override', () => {    beforeEach(() => {      TestBed.configureTestingModule({        imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent],      }).overrideComponent(ParentComponent, {        set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]},      });    });    it('should use Fake Grandchild component', () => {      const fixture = TestBed.createComponent(ParentComponent);      fixture.detectChanges();      expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))');    });  });});////////// Fakes ///////////@Component({  selector: 'child-1',  template: 'Fake Child',})class FakeChildComponent {}@Component({  selector: 'grandchild-1',  template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({  selector: 'child-1',  imports: [FakeGrandchildComponent],  template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService {  override value = 'faked value';}

次に、サービスクラスを引数として TestBed.inject() を呼び出して、テスト内でサービスを注入します。

HELPFUL: TestBed.get() は、Angular バージョン 9 以降で非推奨になりました。 重大な変更を最小限に抑えるため、Angular は TestBed.inject() という新しい関数を導入しました。これは、代わりに使用する必要があります。

      
import {Component, DebugElement, Injectable} from '@angular/core';import {  ComponentFixture,  fakeAsync,  inject,  TestBed,  tick,  waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import {  BankAccountComponent,  BankAccountParentComponent,  Child1Component,  Child2Component,  Child3Component,  ExternalTemplateComponent,  InputComponent,  IoComponent,  IoParentComponent,  LightswitchComponent,  MasterService,  MyIfChildComponent,  MyIfComponent,  MyIfParentComponent,  NeedsContentComponent,  ParentComponent,  ReversePipeComponent,  ShellComponent,  TestProvidersComponent,  TestViewProvidersComponent,  ValueService,} from './demo';export class NotProvided extends ValueService {  /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => {  ////////  Service Tests  /////////////  describe('ValueService', () => {    let service: ValueService;    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});      service = TestBed.inject(ValueService);    });    it('should use ValueService', () => {      service = TestBed.inject(ValueService);      expect(service.getValue()).toBe('real value');    });    it('can inject a default value when service is not provided', () => {      expect(TestBed.inject(NotProvided, null)).toBeNull();    });    it('test should wait for ValueService.getPromiseValue', waitForAsync(() => {      service.getPromiseValue().then((value) => expect(value).toBe('promise value'));    }));    it('test should wait for ValueService.getObservableValue', waitForAsync(() => {      service.getObservableValue().subscribe((value) => expect(value).toBe('observable value'));    }));    // Must use done. See https://github.com/angular/angular/issues/10127    it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => {      service.getObservableDelayValue().subscribe((value) => {        expect(value).toBe('observable delay value');        done();      });    });    it('should allow the use of fakeAsync', fakeAsync(() => {      let value: any;      service.getPromiseValue().then((val: any) => (value = val));      tick(); // Trigger JS engine cycle until all promises resolve.      expect(value).toBe('promise value');    }));  });  describe('MasterService', () => {    let masterService: MasterService;    let valueServiceSpy: jasmine.SpyObj<ValueService>;    beforeEach(() => {      const spy = jasmine.createSpyObj('ValueService', ['getValue']);      TestBed.configureTestingModule({        // Provide both the service-to-test and its (spy) dependency        providers: [MasterService, {provide: ValueService, useValue: spy}],      });      // Inject both the service-to-test and its (spy) dependency      masterService = TestBed.inject(MasterService);      valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>;    });    it('#getValue should return stubbed value from a spy', () => {      const stubValue = 'stub value';      valueServiceSpy.getValue.and.returnValue(stubValue);      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });  });  describe('use inject within `it`', () => {    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});    });    it('should use modified providers', inject([ValueService], (service: ValueService) => {      service.setValue('value modified in beforeEach');      expect(service.getValue()).toBe('value modified in beforeEach');    }));  });  describe('using waitForAsync(inject) within beforeEach', () => {    let serviceValue: string;    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});    });    beforeEach(waitForAsync(      inject([ValueService], (service: ValueService) => {        service.getPromiseValue().then((value) => (serviceValue = value));      }),    ));    it('should use asynchronously modified value ... in synchronous test', () => {      expect(serviceValue).toBe('promise value');    });  });  /////////// Component Tests //////////////////  describe('TestBed component tests', () => {    // beforeEach(waitForAsync(() => {    //   TestBed.configureTestingModule()    //     // Compile everything in DemoModule    //     ;    // }));    it('should create a component with inline template', () => {      const fixture = TestBed.createComponent(Child1Component);      fixture.detectChanges();      expect(fixture).toHaveText('Child');    });    it('should create a component with external template', () => {      const fixture = TestBed.createComponent(ExternalTemplateComponent);      fixture.detectChanges();      expect(fixture).toHaveText('from external template');    });    it('should allow changing members of the component', () => {      const fixture = TestBed.createComponent(MyIfComponent);      fixture.detectChanges();      expect(fixture).toHaveText('MyIf()');      fixture.componentInstance.showMore = true;      fixture.detectChanges();      expect(fixture).toHaveText('MyIf(More)');    });    it('should create a nested component bound to inputs/outputs', () => {      const fixture = TestBed.createComponent(IoParentComponent);      fixture.detectChanges();      const heroes = fixture.debugElement.queryAll(By.css('.hero'));      expect(heroes.length).withContext('has heroes').toBeGreaterThan(0);      const comp = fixture.componentInstance;      const hero = comp.heroes[0];      click(heroes[0]);      fixture.detectChanges();      const selected = fixture.debugElement.query(By.css('p'));      expect(selected).toHaveText(hero.name);    });    it('can access the instance variable of an `*ngFor` row component', () => {      const fixture = TestBed.createComponent(IoParentComponent);      const comp = fixture.componentInstance;      const heroName = comp.heroes[0].name; // first hero's name      fixture.detectChanges();      const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow      const hero = ngForRow.context.hero; // the hero object passed into the row      expect(hero.name).withContext('ngRow.context.hero').toBe(heroName);      const rowComp = ngForRow.componentInstance;      // jasmine.any is an "instance-of-type" test.      expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent));      expect(rowComp.hero.name).withContext('component.hero').toBe(heroName);    });    it('should support clicking a button', () => {      const fixture = TestBed.createComponent(LightswitchComponent);      const btn = fixture.debugElement.query(By.css('button'));      const span = fixture.debugElement.query(By.css('span')).nativeElement;      fixture.detectChanges();      expect(span.textContent)        .withContext('before click')        .toMatch(/is off/i);      click(btn);      fixture.detectChanges();      expect(span.textContent).withContext('after click').toMatch(/is on/i);    });    // ngModel is async so we must wait for it with promise-based `whenStable`    it('should support entering text in input box (ngModel)', waitForAsync(() => {      const expectedOrigName = 'John';      const expectedNewName = 'Sally';      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      expect(comp.name)        .withContext(`At start name should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // wait until ngModel binds comp.name to input box      fixture        .whenStable()        .then(() => {          expect(input.value)            .withContext(              `After ngModel updates input box, input.value should be ${expectedOrigName} `,            )            .toBe(expectedOrigName);          // simulate user entering new name in input          input.value = expectedNewName;          // that change doesn't flow to the component immediately          expect(comp.name())            .withContext(              `comp.name should still be ${expectedOrigName} after value change, before binding happens`,            )            .toBe(expectedOrigName);          // Dispatch a DOM event so that Angular learns of input value change.          // then wait while ngModel pushes input.box value to comp.name          input.dispatchEvent(new Event('input'));          return fixture.whenStable();        })        .then(() => {          expect(comp.name())            .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `)            .toBe(expectedNewName);        });    }));    // fakeAsync version of ngModel input test enables sync test style    // synchronous `tick` replaces asynchronous promise-base `whenStable`    it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => {      const expectedOrigName = 'John';      const expectedNewName = 'Sally';      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      expect(comp.name)        .withContext(`At start name should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // wait until ngModel binds comp.name to input box      tick();      expect(input.value)        .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // simulate user entering new name in input      input.value = expectedNewName;      // that change doesn't flow to the component immediately      expect(comp.name)        .withContext(          `comp.name should still be ${expectedOrigName} after value change, before binding happens`,        )        .toBe(expectedOrigName);      // Dispatch a DOM event so that Angular learns of input value change.      // then wait a tick while ngModel pushes input.box value to comp.name      input.dispatchEvent(new Event('input'));      tick();      expect(comp.name)        .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `)        .toBe(expectedNewName);    }));    it('ReversePipeComp should reverse the input text', fakeAsync(() => {      const inputText = 'the quick brown fox.';      const expectedText = '.xof nworb kciuq eht';      const fixture = TestBed.createComponent(ReversePipeComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement;      // simulate user entering new name in input      input.value = inputText;      // Dispatch a DOM event so that Angular learns of input value change.      // then wait a tick while ngModel pushes input.box value to comp.text      // and Angular updates the output span      input.dispatchEvent(new Event('input'));      tick();      fixture.detectChanges();      expect(span.textContent).withContext('output span').toBe(expectedText);      expect(comp.text).withContext('component.text').toBe(inputText);    }));    // Use this technique to find attached directives of any kind    it('can examine attached directives and listeners', () => {      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const inputEl = fixture.debugElement.query(By.css('input'));      expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel);      const ngControl = inputEl.injector.get(NgControl);      expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl));      expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2);    });    it('BankAccountComponent should set attributes, styles, classes, and properties', () => {      const fixture = TestBed.createComponent(BankAccountParentComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      // the only child is debugElement of the BankAccount component      const el = fixture.debugElement.children[0];      const childComp = el.componentInstance as BankAccountComponent;      expect(childComp).toEqual(jasmine.any(BankAccountComponent));      expect(el.context).withContext('context is the child component').toBe(childComp);      expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id);      expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank);      expect(el.classes['closed']).withContext('closed class').toBe(true);      expect(el.classes['open']).withContext('open class').toBeFalsy();      expect(el.styles['color']).withContext('color style').toBe(comp.color);      expect(el.styles['width'])        .withContext('width style')        .toBe(comp.width + 'px');      // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future?      // expect(el.properties['customProperty']).toBe(true, 'customProperty');    });  });  describe('TestBed component overrides:', () => {    it("should override ChildComp's template", () => {      const fixture = TestBed.configureTestingModule({        imports: [Child1Component],      })        .overrideComponent(Child1Component, {          set: {template: '<span>Fake</span>'},        })        .createComponent(Child1Component);      fixture.detectChanges();      expect(fixture).toHaveText('Fake');    });    it("should override TestProvidersComp's ValueService provider", () => {      const fixture = TestBed.configureTestingModule({        imports: [TestProvidersComponent],      })        .overrideComponent(TestProvidersComponent, {          remove: {providers: [ValueService]},          add: {providers: [{provide: ValueService, useClass: FakeValueService}]},          // Or replace them all (this component has only one provider)          // set:    { providers: [{ provide: ValueService, useClass: FakeValueService }] },        })        .createComponent(TestProvidersComponent);      fixture.detectChanges();      expect(fixture).toHaveText('injected value: faked value', 'text');      // Explore the providerTokens      const tokens = fixture.debugElement.providerTokens;      expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor);      expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent);      expect(tokens).withContext('ValueService').toContain(ValueService);    });    it("should override TestViewProvidersComp's ValueService viewProvider", () => {      const fixture = TestBed.configureTestingModule({        imports: [TestViewProvidersComponent],      })        .overrideComponent(TestViewProvidersComponent, {          // remove: { viewProviders: [ValueService]},          // add:    { viewProviders: [{ provide: ValueService, useClass: FakeValueService }]          // },          // Or replace them all (this component has only one viewProvider)          set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]},        })        .createComponent(TestViewProvidersComponent);      fixture.detectChanges();      expect(fixture).toHaveText('injected value: faked value');    });    it("injected provider should not be same as component's provider", () => {      // TestComponent is parent of TestProvidersComponent      @Component({        template: '<my-service-comp></my-service-comp>',        imports: [TestProvidersComponent],      })      class TestComponent {}      // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent      const fixture = TestBed.configureTestingModule({        imports: [TestComponent, TestProvidersComponent],        providers: [ValueService],      })        .overrideComponent(TestComponent, {          set: {providers: [{provide: ValueService, useValue: {}}]},        })        .overrideComponent(TestProvidersComponent, {          set: {providers: [{provide: ValueService, useClass: FakeValueService}]},        })        .createComponent(TestComponent);      let testBedProvider!: ValueService;      // `inject` uses TestBed's injector      inject([ValueService], (s: ValueService) => (testBedProvider = s))();      const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService;      const tpcProvider = fixture.debugElement.children[0].injector.get(        ValueService,      ) as FakeValueService;      expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider);      expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider);      expect(testBedProvider instanceof ValueService)        .withContext('testBedProvider is ValueService')        .toBe(true);      expect(tcProvider)        .withContext('tcProvider is {}')        .toEqual({} as ValueService);      expect(tpcProvider instanceof FakeValueService)        .withContext('tpcProvider is FakeValueService')        .toBe(true);    });    it('can access template local variables as references', () => {      const fixture = TestBed.configureTestingModule({        imports: [          ShellComponent,          NeedsContentComponent,          Child1Component,          Child2Component,          Child3Component,        ],      })        .overrideComponent(ShellComponent, {          set: {            selector: 'test-shell',            imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component],            template: `          <needs-content #nc>            <child-1 #content text="My"></child-1>            <child-2 #content text="dog"></child-2>            <child-2 text="has"></child-2>            <child-3 #content text="fleas"></child-3>            <div #content>!</div>          </needs-content>          `,          },        })        .createComponent(ShellComponent);      fixture.detectChanges();      // NeedsContentComp is the child of ShellComp      const el = fixture.debugElement.children[0];      const comp = el.componentInstance;      expect(comp.children.toArray().length)        .withContext('three different child components and an ElementRef with #content')        .toBe(4);      expect(el.references['nc']).withContext('#nc reference to component').toBe(comp);      // Filter for DebugElements with a #content reference      const contentRefs = el.queryAll((de) => de.references['content']);      expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4);    });  });  describe('nested (one-deep) component override', () => {    beforeEach(() => {      TestBed.configureTestingModule({        imports: [ParentComponent, FakeChildComponent],      }).overrideComponent(ParentComponent, {        set: {imports: [FakeChildComponent]},      });    });    it('ParentComp should use Fake Child component', () => {      const fixture = TestBed.createComponent(ParentComponent);      fixture.detectChanges();      expect(fixture).toHaveText('Parent(Fake Child)');    });  });  describe('nested (two-deep) component override', () => {    beforeEach(() => {      TestBed.configureTestingModule({        imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent],      }).overrideComponent(ParentComponent, {        set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]},      });    });    it('should use Fake Grandchild component', () => {      const fixture = TestBed.createComponent(ParentComponent);      fixture.detectChanges();      expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))');    });  });});////////// Fakes ///////////@Component({  selector: 'child-1',  template: 'Fake Child',})class FakeChildComponent {}@Component({  selector: 'grandchild-1',  template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({  selector: 'child-1',  imports: [FakeGrandchildComponent],  template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService {  override value = 'faked value';}

または、セットアップの一部としてサービスを注入したい場合は、beforeEach() 内で行います。

      
import {Component, DebugElement, Injectable} from '@angular/core';import {  ComponentFixture,  fakeAsync,  inject,  TestBed,  tick,  waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import {  BankAccountComponent,  BankAccountParentComponent,  Child1Component,  Child2Component,  Child3Component,  ExternalTemplateComponent,  InputComponent,  IoComponent,  IoParentComponent,  LightswitchComponent,  MasterService,  MyIfChildComponent,  MyIfComponent,  MyIfParentComponent,  NeedsContentComponent,  ParentComponent,  ReversePipeComponent,  ShellComponent,  TestProvidersComponent,  TestViewProvidersComponent,  ValueService,} from './demo';export class NotProvided extends ValueService {  /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => {  ////////  Service Tests  /////////////  describe('ValueService', () => {    let service: ValueService;    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});      service = TestBed.inject(ValueService);    });    it('should use ValueService', () => {      service = TestBed.inject(ValueService);      expect(service.getValue()).toBe('real value');    });    it('can inject a default value when service is not provided', () => {      expect(TestBed.inject(NotProvided, null)).toBeNull();    });    it('test should wait for ValueService.getPromiseValue', waitForAsync(() => {      service.getPromiseValue().then((value) => expect(value).toBe('promise value'));    }));    it('test should wait for ValueService.getObservableValue', waitForAsync(() => {      service.getObservableValue().subscribe((value) => expect(value).toBe('observable value'));    }));    // Must use done. See https://github.com/angular/angular/issues/10127    it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => {      service.getObservableDelayValue().subscribe((value) => {        expect(value).toBe('observable delay value');        done();      });    });    it('should allow the use of fakeAsync', fakeAsync(() => {      let value: any;      service.getPromiseValue().then((val: any) => (value = val));      tick(); // Trigger JS engine cycle until all promises resolve.      expect(value).toBe('promise value');    }));  });  describe('MasterService', () => {    let masterService: MasterService;    let valueServiceSpy: jasmine.SpyObj<ValueService>;    beforeEach(() => {      const spy = jasmine.createSpyObj('ValueService', ['getValue']);      TestBed.configureTestingModule({        // Provide both the service-to-test and its (spy) dependency        providers: [MasterService, {provide: ValueService, useValue: spy}],      });      // Inject both the service-to-test and its (spy) dependency      masterService = TestBed.inject(MasterService);      valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>;    });    it('#getValue should return stubbed value from a spy', () => {      const stubValue = 'stub value';      valueServiceSpy.getValue.and.returnValue(stubValue);      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });  });  describe('use inject within `it`', () => {    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});    });    it('should use modified providers', inject([ValueService], (service: ValueService) => {      service.setValue('value modified in beforeEach');      expect(service.getValue()).toBe('value modified in beforeEach');    }));  });  describe('using waitForAsync(inject) within beforeEach', () => {    let serviceValue: string;    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});    });    beforeEach(waitForAsync(      inject([ValueService], (service: ValueService) => {        service.getPromiseValue().then((value) => (serviceValue = value));      }),    ));    it('should use asynchronously modified value ... in synchronous test', () => {      expect(serviceValue).toBe('promise value');    });  });  /////////// Component Tests //////////////////  describe('TestBed component tests', () => {    // beforeEach(waitForAsync(() => {    //   TestBed.configureTestingModule()    //     // Compile everything in DemoModule    //     ;    // }));    it('should create a component with inline template', () => {      const fixture = TestBed.createComponent(Child1Component);      fixture.detectChanges();      expect(fixture).toHaveText('Child');    });    it('should create a component with external template', () => {      const fixture = TestBed.createComponent(ExternalTemplateComponent);      fixture.detectChanges();      expect(fixture).toHaveText('from external template');    });    it('should allow changing members of the component', () => {      const fixture = TestBed.createComponent(MyIfComponent);      fixture.detectChanges();      expect(fixture).toHaveText('MyIf()');      fixture.componentInstance.showMore = true;      fixture.detectChanges();      expect(fixture).toHaveText('MyIf(More)');    });    it('should create a nested component bound to inputs/outputs', () => {      const fixture = TestBed.createComponent(IoParentComponent);      fixture.detectChanges();      const heroes = fixture.debugElement.queryAll(By.css('.hero'));      expect(heroes.length).withContext('has heroes').toBeGreaterThan(0);      const comp = fixture.componentInstance;      const hero = comp.heroes[0];      click(heroes[0]);      fixture.detectChanges();      const selected = fixture.debugElement.query(By.css('p'));      expect(selected).toHaveText(hero.name);    });    it('can access the instance variable of an `*ngFor` row component', () => {      const fixture = TestBed.createComponent(IoParentComponent);      const comp = fixture.componentInstance;      const heroName = comp.heroes[0].name; // first hero's name      fixture.detectChanges();      const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow      const hero = ngForRow.context.hero; // the hero object passed into the row      expect(hero.name).withContext('ngRow.context.hero').toBe(heroName);      const rowComp = ngForRow.componentInstance;      // jasmine.any is an "instance-of-type" test.      expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent));      expect(rowComp.hero.name).withContext('component.hero').toBe(heroName);    });    it('should support clicking a button', () => {      const fixture = TestBed.createComponent(LightswitchComponent);      const btn = fixture.debugElement.query(By.css('button'));      const span = fixture.debugElement.query(By.css('span')).nativeElement;      fixture.detectChanges();      expect(span.textContent)        .withContext('before click')        .toMatch(/is off/i);      click(btn);      fixture.detectChanges();      expect(span.textContent).withContext('after click').toMatch(/is on/i);    });    // ngModel is async so we must wait for it with promise-based `whenStable`    it('should support entering text in input box (ngModel)', waitForAsync(() => {      const expectedOrigName = 'John';      const expectedNewName = 'Sally';      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      expect(comp.name)        .withContext(`At start name should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // wait until ngModel binds comp.name to input box      fixture        .whenStable()        .then(() => {          expect(input.value)            .withContext(              `After ngModel updates input box, input.value should be ${expectedOrigName} `,            )            .toBe(expectedOrigName);          // simulate user entering new name in input          input.value = expectedNewName;          // that change doesn't flow to the component immediately          expect(comp.name())            .withContext(              `comp.name should still be ${expectedOrigName} after value change, before binding happens`,            )            .toBe(expectedOrigName);          // Dispatch a DOM event so that Angular learns of input value change.          // then wait while ngModel pushes input.box value to comp.name          input.dispatchEvent(new Event('input'));          return fixture.whenStable();        })        .then(() => {          expect(comp.name())            .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `)            .toBe(expectedNewName);        });    }));    // fakeAsync version of ngModel input test enables sync test style    // synchronous `tick` replaces asynchronous promise-base `whenStable`    it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => {      const expectedOrigName = 'John';      const expectedNewName = 'Sally';      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      expect(comp.name)        .withContext(`At start name should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // wait until ngModel binds comp.name to input box      tick();      expect(input.value)        .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // simulate user entering new name in input      input.value = expectedNewName;      // that change doesn't flow to the component immediately      expect(comp.name)        .withContext(          `comp.name should still be ${expectedOrigName} after value change, before binding happens`,        )        .toBe(expectedOrigName);      // Dispatch a DOM event so that Angular learns of input value change.      // then wait a tick while ngModel pushes input.box value to comp.name      input.dispatchEvent(new Event('input'));      tick();      expect(comp.name)        .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `)        .toBe(expectedNewName);    }));    it('ReversePipeComp should reverse the input text', fakeAsync(() => {      const inputText = 'the quick brown fox.';      const expectedText = '.xof nworb kciuq eht';      const fixture = TestBed.createComponent(ReversePipeComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement;      // simulate user entering new name in input      input.value = inputText;      // Dispatch a DOM event so that Angular learns of input value change.      // then wait a tick while ngModel pushes input.box value to comp.text      // and Angular updates the output span      input.dispatchEvent(new Event('input'));      tick();      fixture.detectChanges();      expect(span.textContent).withContext('output span').toBe(expectedText);      expect(comp.text).withContext('component.text').toBe(inputText);    }));    // Use this technique to find attached directives of any kind    it('can examine attached directives and listeners', () => {      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const inputEl = fixture.debugElement.query(By.css('input'));      expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel);      const ngControl = inputEl.injector.get(NgControl);      expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl));      expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2);    });    it('BankAccountComponent should set attributes, styles, classes, and properties', () => {      const fixture = TestBed.createComponent(BankAccountParentComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      // the only child is debugElement of the BankAccount component      const el = fixture.debugElement.children[0];      const childComp = el.componentInstance as BankAccountComponent;      expect(childComp).toEqual(jasmine.any(BankAccountComponent));      expect(el.context).withContext('context is the child component').toBe(childComp);      expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id);      expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank);      expect(el.classes['closed']).withContext('closed class').toBe(true);      expect(el.classes['open']).withContext('open class').toBeFalsy();      expect(el.styles['color']).withContext('color style').toBe(comp.color);      expect(el.styles['width'])        .withContext('width style')        .toBe(comp.width + 'px');      // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future?      // expect(el.properties['customProperty']).toBe(true, 'customProperty');    });  });  describe('TestBed component overrides:', () => {    it("should override ChildComp's template", () => {      const fixture = TestBed.configureTestingModule({        imports: [Child1Component],      })        .overrideComponent(Child1Component, {          set: {template: '<span>Fake</span>'},        })        .createComponent(Child1Component);      fixture.detectChanges();      expect(fixture).toHaveText('Fake');    });    it("should override TestProvidersComp's ValueService provider", () => {      const fixture = TestBed.configureTestingModule({        imports: [TestProvidersComponent],      })        .overrideComponent(TestProvidersComponent, {          remove: {providers: [ValueService]},          add: {providers: [{provide: ValueService, useClass: FakeValueService}]},          // Or replace them all (this component has only one provider)          // set:    { providers: [{ provide: ValueService, useClass: FakeValueService }] },        })        .createComponent(TestProvidersComponent);      fixture.detectChanges();      expect(fixture).toHaveText('injected value: faked value', 'text');      // Explore the providerTokens      const tokens = fixture.debugElement.providerTokens;      expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor);      expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent);      expect(tokens).withContext('ValueService').toContain(ValueService);    });    it("should override TestViewProvidersComp's ValueService viewProvider", () => {      const fixture = TestBed.configureTestingModule({        imports: [TestViewProvidersComponent],      })        .overrideComponent(TestViewProvidersComponent, {          // remove: { viewProviders: [ValueService]},          // add:    { viewProviders: [{ provide: ValueService, useClass: FakeValueService }]          // },          // Or replace them all (this component has only one viewProvider)          set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]},        })        .createComponent(TestViewProvidersComponent);      fixture.detectChanges();      expect(fixture).toHaveText('injected value: faked value');    });    it("injected provider should not be same as component's provider", () => {      // TestComponent is parent of TestProvidersComponent      @Component({        template: '<my-service-comp></my-service-comp>',        imports: [TestProvidersComponent],      })      class TestComponent {}      // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent      const fixture = TestBed.configureTestingModule({        imports: [TestComponent, TestProvidersComponent],        providers: [ValueService],      })        .overrideComponent(TestComponent, {          set: {providers: [{provide: ValueService, useValue: {}}]},        })        .overrideComponent(TestProvidersComponent, {          set: {providers: [{provide: ValueService, useClass: FakeValueService}]},        })        .createComponent(TestComponent);      let testBedProvider!: ValueService;      // `inject` uses TestBed's injector      inject([ValueService], (s: ValueService) => (testBedProvider = s))();      const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService;      const tpcProvider = fixture.debugElement.children[0].injector.get(        ValueService,      ) as FakeValueService;      expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider);      expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider);      expect(testBedProvider instanceof ValueService)        .withContext('testBedProvider is ValueService')        .toBe(true);      expect(tcProvider)        .withContext('tcProvider is {}')        .toEqual({} as ValueService);      expect(tpcProvider instanceof FakeValueService)        .withContext('tpcProvider is FakeValueService')        .toBe(true);    });    it('can access template local variables as references', () => {      const fixture = TestBed.configureTestingModule({        imports: [          ShellComponent,          NeedsContentComponent,          Child1Component,          Child2Component,          Child3Component,        ],      })        .overrideComponent(ShellComponent, {          set: {            selector: 'test-shell',            imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component],            template: `          <needs-content #nc>            <child-1 #content text="My"></child-1>            <child-2 #content text="dog"></child-2>            <child-2 text="has"></child-2>            <child-3 #content text="fleas"></child-3>            <div #content>!</div>          </needs-content>          `,          },        })        .createComponent(ShellComponent);      fixture.detectChanges();      // NeedsContentComp is the child of ShellComp      const el = fixture.debugElement.children[0];      const comp = el.componentInstance;      expect(comp.children.toArray().length)        .withContext('three different child components and an ElementRef with #content')        .toBe(4);      expect(el.references['nc']).withContext('#nc reference to component').toBe(comp);      // Filter for DebugElements with a #content reference      const contentRefs = el.queryAll((de) => de.references['content']);      expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4);    });  });  describe('nested (one-deep) component override', () => {    beforeEach(() => {      TestBed.configureTestingModule({        imports: [ParentComponent, FakeChildComponent],      }).overrideComponent(ParentComponent, {        set: {imports: [FakeChildComponent]},      });    });    it('ParentComp should use Fake Child component', () => {      const fixture = TestBed.createComponent(ParentComponent);      fixture.detectChanges();      expect(fixture).toHaveText('Parent(Fake Child)');    });  });  describe('nested (two-deep) component override', () => {    beforeEach(() => {      TestBed.configureTestingModule({        imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent],      }).overrideComponent(ParentComponent, {        set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]},      });    });    it('should use Fake Grandchild component', () => {      const fixture = TestBed.createComponent(ParentComponent);      fixture.detectChanges();      expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))');    });  });});////////// Fakes ///////////@Component({  selector: 'child-1',  template: 'Fake Child',})class FakeChildComponent {}@Component({  selector: 'grandchild-1',  template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({  selector: 'child-1',  imports: [FakeGrandchildComponent],  template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService {  override value = 'faked value';}

依存関係のあるサービスをテストする場合は、providers 配列にモックを提供します。

次の例では、モックはスパイオブジェクトです。

      
import {Component, DebugElement, Injectable} from '@angular/core';import {  ComponentFixture,  fakeAsync,  inject,  TestBed,  tick,  waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import {  BankAccountComponent,  BankAccountParentComponent,  Child1Component,  Child2Component,  Child3Component,  ExternalTemplateComponent,  InputComponent,  IoComponent,  IoParentComponent,  LightswitchComponent,  MasterService,  MyIfChildComponent,  MyIfComponent,  MyIfParentComponent,  NeedsContentComponent,  ParentComponent,  ReversePipeComponent,  ShellComponent,  TestProvidersComponent,  TestViewProvidersComponent,  ValueService,} from './demo';export class NotProvided extends ValueService {  /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => {  ////////  Service Tests  /////////////  describe('ValueService', () => {    let service: ValueService;    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});      service = TestBed.inject(ValueService);    });    it('should use ValueService', () => {      service = TestBed.inject(ValueService);      expect(service.getValue()).toBe('real value');    });    it('can inject a default value when service is not provided', () => {      expect(TestBed.inject(NotProvided, null)).toBeNull();    });    it('test should wait for ValueService.getPromiseValue', waitForAsync(() => {      service.getPromiseValue().then((value) => expect(value).toBe('promise value'));    }));    it('test should wait for ValueService.getObservableValue', waitForAsync(() => {      service.getObservableValue().subscribe((value) => expect(value).toBe('observable value'));    }));    // Must use done. See https://github.com/angular/angular/issues/10127    it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => {      service.getObservableDelayValue().subscribe((value) => {        expect(value).toBe('observable delay value');        done();      });    });    it('should allow the use of fakeAsync', fakeAsync(() => {      let value: any;      service.getPromiseValue().then((val: any) => (value = val));      tick(); // Trigger JS engine cycle until all promises resolve.      expect(value).toBe('promise value');    }));  });  describe('MasterService', () => {    let masterService: MasterService;    let valueServiceSpy: jasmine.SpyObj<ValueService>;    beforeEach(() => {      const spy = jasmine.createSpyObj('ValueService', ['getValue']);      TestBed.configureTestingModule({        // Provide both the service-to-test and its (spy) dependency        providers: [MasterService, {provide: ValueService, useValue: spy}],      });      // Inject both the service-to-test and its (spy) dependency      masterService = TestBed.inject(MasterService);      valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>;    });    it('#getValue should return stubbed value from a spy', () => {      const stubValue = 'stub value';      valueServiceSpy.getValue.and.returnValue(stubValue);      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });  });  describe('use inject within `it`', () => {    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});    });    it('should use modified providers', inject([ValueService], (service: ValueService) => {      service.setValue('value modified in beforeEach');      expect(service.getValue()).toBe('value modified in beforeEach');    }));  });  describe('using waitForAsync(inject) within beforeEach', () => {    let serviceValue: string;    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});    });    beforeEach(waitForAsync(      inject([ValueService], (service: ValueService) => {        service.getPromiseValue().then((value) => (serviceValue = value));      }),    ));    it('should use asynchronously modified value ... in synchronous test', () => {      expect(serviceValue).toBe('promise value');    });  });  /////////// Component Tests //////////////////  describe('TestBed component tests', () => {    // beforeEach(waitForAsync(() => {    //   TestBed.configureTestingModule()    //     // Compile everything in DemoModule    //     ;    // }));    it('should create a component with inline template', () => {      const fixture = TestBed.createComponent(Child1Component);      fixture.detectChanges();      expect(fixture).toHaveText('Child');    });    it('should create a component with external template', () => {      const fixture = TestBed.createComponent(ExternalTemplateComponent);      fixture.detectChanges();      expect(fixture).toHaveText('from external template');    });    it('should allow changing members of the component', () => {      const fixture = TestBed.createComponent(MyIfComponent);      fixture.detectChanges();      expect(fixture).toHaveText('MyIf()');      fixture.componentInstance.showMore = true;      fixture.detectChanges();      expect(fixture).toHaveText('MyIf(More)');    });    it('should create a nested component bound to inputs/outputs', () => {      const fixture = TestBed.createComponent(IoParentComponent);      fixture.detectChanges();      const heroes = fixture.debugElement.queryAll(By.css('.hero'));      expect(heroes.length).withContext('has heroes').toBeGreaterThan(0);      const comp = fixture.componentInstance;      const hero = comp.heroes[0];      click(heroes[0]);      fixture.detectChanges();      const selected = fixture.debugElement.query(By.css('p'));      expect(selected).toHaveText(hero.name);    });    it('can access the instance variable of an `*ngFor` row component', () => {      const fixture = TestBed.createComponent(IoParentComponent);      const comp = fixture.componentInstance;      const heroName = comp.heroes[0].name; // first hero's name      fixture.detectChanges();      const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow      const hero = ngForRow.context.hero; // the hero object passed into the row      expect(hero.name).withContext('ngRow.context.hero').toBe(heroName);      const rowComp = ngForRow.componentInstance;      // jasmine.any is an "instance-of-type" test.      expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent));      expect(rowComp.hero.name).withContext('component.hero').toBe(heroName);    });    it('should support clicking a button', () => {      const fixture = TestBed.createComponent(LightswitchComponent);      const btn = fixture.debugElement.query(By.css('button'));      const span = fixture.debugElement.query(By.css('span')).nativeElement;      fixture.detectChanges();      expect(span.textContent)        .withContext('before click')        .toMatch(/is off/i);      click(btn);      fixture.detectChanges();      expect(span.textContent).withContext('after click').toMatch(/is on/i);    });    // ngModel is async so we must wait for it with promise-based `whenStable`    it('should support entering text in input box (ngModel)', waitForAsync(() => {      const expectedOrigName = 'John';      const expectedNewName = 'Sally';      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      expect(comp.name)        .withContext(`At start name should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // wait until ngModel binds comp.name to input box      fixture        .whenStable()        .then(() => {          expect(input.value)            .withContext(              `After ngModel updates input box, input.value should be ${expectedOrigName} `,            )            .toBe(expectedOrigName);          // simulate user entering new name in input          input.value = expectedNewName;          // that change doesn't flow to the component immediately          expect(comp.name())            .withContext(              `comp.name should still be ${expectedOrigName} after value change, before binding happens`,            )            .toBe(expectedOrigName);          // Dispatch a DOM event so that Angular learns of input value change.          // then wait while ngModel pushes input.box value to comp.name          input.dispatchEvent(new Event('input'));          return fixture.whenStable();        })        .then(() => {          expect(comp.name())            .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `)            .toBe(expectedNewName);        });    }));    // fakeAsync version of ngModel input test enables sync test style    // synchronous `tick` replaces asynchronous promise-base `whenStable`    it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => {      const expectedOrigName = 'John';      const expectedNewName = 'Sally';      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      expect(comp.name)        .withContext(`At start name should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // wait until ngModel binds comp.name to input box      tick();      expect(input.value)        .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // simulate user entering new name in input      input.value = expectedNewName;      // that change doesn't flow to the component immediately      expect(comp.name)        .withContext(          `comp.name should still be ${expectedOrigName} after value change, before binding happens`,        )        .toBe(expectedOrigName);      // Dispatch a DOM event so that Angular learns of input value change.      // then wait a tick while ngModel pushes input.box value to comp.name      input.dispatchEvent(new Event('input'));      tick();      expect(comp.name)        .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `)        .toBe(expectedNewName);    }));    it('ReversePipeComp should reverse the input text', fakeAsync(() => {      const inputText = 'the quick brown fox.';      const expectedText = '.xof nworb kciuq eht';      const fixture = TestBed.createComponent(ReversePipeComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement;      // simulate user entering new name in input      input.value = inputText;      // Dispatch a DOM event so that Angular learns of input value change.      // then wait a tick while ngModel pushes input.box value to comp.text      // and Angular updates the output span      input.dispatchEvent(new Event('input'));      tick();      fixture.detectChanges();      expect(span.textContent).withContext('output span').toBe(expectedText);      expect(comp.text).withContext('component.text').toBe(inputText);    }));    // Use this technique to find attached directives of any kind    it('can examine attached directives and listeners', () => {      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const inputEl = fixture.debugElement.query(By.css('input'));      expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel);      const ngControl = inputEl.injector.get(NgControl);      expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl));      expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2);    });    it('BankAccountComponent should set attributes, styles, classes, and properties', () => {      const fixture = TestBed.createComponent(BankAccountParentComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      // the only child is debugElement of the BankAccount component      const el = fixture.debugElement.children[0];      const childComp = el.componentInstance as BankAccountComponent;      expect(childComp).toEqual(jasmine.any(BankAccountComponent));      expect(el.context).withContext('context is the child component').toBe(childComp);      expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id);      expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank);      expect(el.classes['closed']).withContext('closed class').toBe(true);      expect(el.classes['open']).withContext('open class').toBeFalsy();      expect(el.styles['color']).withContext('color style').toBe(comp.color);      expect(el.styles['width'])        .withContext('width style')        .toBe(comp.width + 'px');      // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future?      // expect(el.properties['customProperty']).toBe(true, 'customProperty');    });  });  describe('TestBed component overrides:', () => {    it("should override ChildComp's template", () => {      const fixture = TestBed.configureTestingModule({        imports: [Child1Component],      })        .overrideComponent(Child1Component, {          set: {template: '<span>Fake</span>'},        })        .createComponent(Child1Component);      fixture.detectChanges();      expect(fixture).toHaveText('Fake');    });    it("should override TestProvidersComp's ValueService provider", () => {      const fixture = TestBed.configureTestingModule({        imports: [TestProvidersComponent],      })        .overrideComponent(TestProvidersComponent, {          remove: {providers: [ValueService]},          add: {providers: [{provide: ValueService, useClass: FakeValueService}]},          // Or replace them all (this component has only one provider)          // set:    { providers: [{ provide: ValueService, useClass: FakeValueService }] },        })        .createComponent(TestProvidersComponent);      fixture.detectChanges();      expect(fixture).toHaveText('injected value: faked value', 'text');      // Explore the providerTokens      const tokens = fixture.debugElement.providerTokens;      expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor);      expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent);      expect(tokens).withContext('ValueService').toContain(ValueService);    });    it("should override TestViewProvidersComp's ValueService viewProvider", () => {      const fixture = TestBed.configureTestingModule({        imports: [TestViewProvidersComponent],      })        .overrideComponent(TestViewProvidersComponent, {          // remove: { viewProviders: [ValueService]},          // add:    { viewProviders: [{ provide: ValueService, useClass: FakeValueService }]          // },          // Or replace them all (this component has only one viewProvider)          set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]},        })        .createComponent(TestViewProvidersComponent);      fixture.detectChanges();      expect(fixture).toHaveText('injected value: faked value');    });    it("injected provider should not be same as component's provider", () => {      // TestComponent is parent of TestProvidersComponent      @Component({        template: '<my-service-comp></my-service-comp>',        imports: [TestProvidersComponent],      })      class TestComponent {}      // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent      const fixture = TestBed.configureTestingModule({        imports: [TestComponent, TestProvidersComponent],        providers: [ValueService],      })        .overrideComponent(TestComponent, {          set: {providers: [{provide: ValueService, useValue: {}}]},        })        .overrideComponent(TestProvidersComponent, {          set: {providers: [{provide: ValueService, useClass: FakeValueService}]},        })        .createComponent(TestComponent);      let testBedProvider!: ValueService;      // `inject` uses TestBed's injector      inject([ValueService], (s: ValueService) => (testBedProvider = s))();      const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService;      const tpcProvider = fixture.debugElement.children[0].injector.get(        ValueService,      ) as FakeValueService;      expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider);      expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider);      expect(testBedProvider instanceof ValueService)        .withContext('testBedProvider is ValueService')        .toBe(true);      expect(tcProvider)        .withContext('tcProvider is {}')        .toEqual({} as ValueService);      expect(tpcProvider instanceof FakeValueService)        .withContext('tpcProvider is FakeValueService')        .toBe(true);    });    it('can access template local variables as references', () => {      const fixture = TestBed.configureTestingModule({        imports: [          ShellComponent,          NeedsContentComponent,          Child1Component,          Child2Component,          Child3Component,        ],      })        .overrideComponent(ShellComponent, {          set: {            selector: 'test-shell',            imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component],            template: `          <needs-content #nc>            <child-1 #content text="My"></child-1>            <child-2 #content text="dog"></child-2>            <child-2 text="has"></child-2>            <child-3 #content text="fleas"></child-3>            <div #content>!</div>          </needs-content>          `,          },        })        .createComponent(ShellComponent);      fixture.detectChanges();      // NeedsContentComp is the child of ShellComp      const el = fixture.debugElement.children[0];      const comp = el.componentInstance;      expect(comp.children.toArray().length)        .withContext('three different child components and an ElementRef with #content')        .toBe(4);      expect(el.references['nc']).withContext('#nc reference to component').toBe(comp);      // Filter for DebugElements with a #content reference      const contentRefs = el.queryAll((de) => de.references['content']);      expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4);    });  });  describe('nested (one-deep) component override', () => {    beforeEach(() => {      TestBed.configureTestingModule({        imports: [ParentComponent, FakeChildComponent],      }).overrideComponent(ParentComponent, {        set: {imports: [FakeChildComponent]},      });    });    it('ParentComp should use Fake Child component', () => {      const fixture = TestBed.createComponent(ParentComponent);      fixture.detectChanges();      expect(fixture).toHaveText('Parent(Fake Child)');    });  });  describe('nested (two-deep) component override', () => {    beforeEach(() => {      TestBed.configureTestingModule({        imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent],      }).overrideComponent(ParentComponent, {        set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]},      });    });    it('should use Fake Grandchild component', () => {      const fixture = TestBed.createComponent(ParentComponent);      fixture.detectChanges();      expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))');    });  });});////////// Fakes ///////////@Component({  selector: 'child-1',  template: 'Fake Child',})class FakeChildComponent {}@Component({  selector: 'grandchild-1',  template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({  selector: 'child-1',  imports: [FakeGrandchildComponent],  template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService {  override value = 'faked value';}

テストでは、以前と同じように、そのスパイを使用します。

      
import {Component, DebugElement, Injectable} from '@angular/core';import {  ComponentFixture,  fakeAsync,  inject,  TestBed,  tick,  waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import {  BankAccountComponent,  BankAccountParentComponent,  Child1Component,  Child2Component,  Child3Component,  ExternalTemplateComponent,  InputComponent,  IoComponent,  IoParentComponent,  LightswitchComponent,  MasterService,  MyIfChildComponent,  MyIfComponent,  MyIfParentComponent,  NeedsContentComponent,  ParentComponent,  ReversePipeComponent,  ShellComponent,  TestProvidersComponent,  TestViewProvidersComponent,  ValueService,} from './demo';export class NotProvided extends ValueService {  /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => {  ////////  Service Tests  /////////////  describe('ValueService', () => {    let service: ValueService;    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});      service = TestBed.inject(ValueService);    });    it('should use ValueService', () => {      service = TestBed.inject(ValueService);      expect(service.getValue()).toBe('real value');    });    it('can inject a default value when service is not provided', () => {      expect(TestBed.inject(NotProvided, null)).toBeNull();    });    it('test should wait for ValueService.getPromiseValue', waitForAsync(() => {      service.getPromiseValue().then((value) => expect(value).toBe('promise value'));    }));    it('test should wait for ValueService.getObservableValue', waitForAsync(() => {      service.getObservableValue().subscribe((value) => expect(value).toBe('observable value'));    }));    // Must use done. See https://github.com/angular/angular/issues/10127    it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => {      service.getObservableDelayValue().subscribe((value) => {        expect(value).toBe('observable delay value');        done();      });    });    it('should allow the use of fakeAsync', fakeAsync(() => {      let value: any;      service.getPromiseValue().then((val: any) => (value = val));      tick(); // Trigger JS engine cycle until all promises resolve.      expect(value).toBe('promise value');    }));  });  describe('MasterService', () => {    let masterService: MasterService;    let valueServiceSpy: jasmine.SpyObj<ValueService>;    beforeEach(() => {      const spy = jasmine.createSpyObj('ValueService', ['getValue']);      TestBed.configureTestingModule({        // Provide both the service-to-test and its (spy) dependency        providers: [MasterService, {provide: ValueService, useValue: spy}],      });      // Inject both the service-to-test and its (spy) dependency      masterService = TestBed.inject(MasterService);      valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>;    });    it('#getValue should return stubbed value from a spy', () => {      const stubValue = 'stub value';      valueServiceSpy.getValue.and.returnValue(stubValue);      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });  });  describe('use inject within `it`', () => {    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});    });    it('should use modified providers', inject([ValueService], (service: ValueService) => {      service.setValue('value modified in beforeEach');      expect(service.getValue()).toBe('value modified in beforeEach');    }));  });  describe('using waitForAsync(inject) within beforeEach', () => {    let serviceValue: string;    beforeEach(() => {      TestBed.configureTestingModule({providers: [ValueService]});    });    beforeEach(waitForAsync(      inject([ValueService], (service: ValueService) => {        service.getPromiseValue().then((value) => (serviceValue = value));      }),    ));    it('should use asynchronously modified value ... in synchronous test', () => {      expect(serviceValue).toBe('promise value');    });  });  /////////// Component Tests //////////////////  describe('TestBed component tests', () => {    // beforeEach(waitForAsync(() => {    //   TestBed.configureTestingModule()    //     // Compile everything in DemoModule    //     ;    // }));    it('should create a component with inline template', () => {      const fixture = TestBed.createComponent(Child1Component);      fixture.detectChanges();      expect(fixture).toHaveText('Child');    });    it('should create a component with external template', () => {      const fixture = TestBed.createComponent(ExternalTemplateComponent);      fixture.detectChanges();      expect(fixture).toHaveText('from external template');    });    it('should allow changing members of the component', () => {      const fixture = TestBed.createComponent(MyIfComponent);      fixture.detectChanges();      expect(fixture).toHaveText('MyIf()');      fixture.componentInstance.showMore = true;      fixture.detectChanges();      expect(fixture).toHaveText('MyIf(More)');    });    it('should create a nested component bound to inputs/outputs', () => {      const fixture = TestBed.createComponent(IoParentComponent);      fixture.detectChanges();      const heroes = fixture.debugElement.queryAll(By.css('.hero'));      expect(heroes.length).withContext('has heroes').toBeGreaterThan(0);      const comp = fixture.componentInstance;      const hero = comp.heroes[0];      click(heroes[0]);      fixture.detectChanges();      const selected = fixture.debugElement.query(By.css('p'));      expect(selected).toHaveText(hero.name);    });    it('can access the instance variable of an `*ngFor` row component', () => {      const fixture = TestBed.createComponent(IoParentComponent);      const comp = fixture.componentInstance;      const heroName = comp.heroes[0].name; // first hero's name      fixture.detectChanges();      const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow      const hero = ngForRow.context.hero; // the hero object passed into the row      expect(hero.name).withContext('ngRow.context.hero').toBe(heroName);      const rowComp = ngForRow.componentInstance;      // jasmine.any is an "instance-of-type" test.      expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent));      expect(rowComp.hero.name).withContext('component.hero').toBe(heroName);    });    it('should support clicking a button', () => {      const fixture = TestBed.createComponent(LightswitchComponent);      const btn = fixture.debugElement.query(By.css('button'));      const span = fixture.debugElement.query(By.css('span')).nativeElement;      fixture.detectChanges();      expect(span.textContent)        .withContext('before click')        .toMatch(/is off/i);      click(btn);      fixture.detectChanges();      expect(span.textContent).withContext('after click').toMatch(/is on/i);    });    // ngModel is async so we must wait for it with promise-based `whenStable`    it('should support entering text in input box (ngModel)', waitForAsync(() => {      const expectedOrigName = 'John';      const expectedNewName = 'Sally';      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      expect(comp.name)        .withContext(`At start name should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // wait until ngModel binds comp.name to input box      fixture        .whenStable()        .then(() => {          expect(input.value)            .withContext(              `After ngModel updates input box, input.value should be ${expectedOrigName} `,            )            .toBe(expectedOrigName);          // simulate user entering new name in input          input.value = expectedNewName;          // that change doesn't flow to the component immediately          expect(comp.name())            .withContext(              `comp.name should still be ${expectedOrigName} after value change, before binding happens`,            )            .toBe(expectedOrigName);          // Dispatch a DOM event so that Angular learns of input value change.          // then wait while ngModel pushes input.box value to comp.name          input.dispatchEvent(new Event('input'));          return fixture.whenStable();        })        .then(() => {          expect(comp.name())            .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `)            .toBe(expectedNewName);        });    }));    // fakeAsync version of ngModel input test enables sync test style    // synchronous `tick` replaces asynchronous promise-base `whenStable`    it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => {      const expectedOrigName = 'John';      const expectedNewName = 'Sally';      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      expect(comp.name)        .withContext(`At start name should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // wait until ngModel binds comp.name to input box      tick();      expect(input.value)        .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `)        .toBe(expectedOrigName);      // simulate user entering new name in input      input.value = expectedNewName;      // that change doesn't flow to the component immediately      expect(comp.name)        .withContext(          `comp.name should still be ${expectedOrigName} after value change, before binding happens`,        )        .toBe(expectedOrigName);      // Dispatch a DOM event so that Angular learns of input value change.      // then wait a tick while ngModel pushes input.box value to comp.name      input.dispatchEvent(new Event('input'));      tick();      expect(comp.name)        .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `)        .toBe(expectedNewName);    }));    it('ReversePipeComp should reverse the input text', fakeAsync(() => {      const inputText = 'the quick brown fox.';      const expectedText = '.xof nworb kciuq eht';      const fixture = TestBed.createComponent(ReversePipeComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;      const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement;      // simulate user entering new name in input      input.value = inputText;      // Dispatch a DOM event so that Angular learns of input value change.      // then wait a tick while ngModel pushes input.box value to comp.text      // and Angular updates the output span      input.dispatchEvent(new Event('input'));      tick();      fixture.detectChanges();      expect(span.textContent).withContext('output span').toBe(expectedText);      expect(comp.text).withContext('component.text').toBe(inputText);    }));    // Use this technique to find attached directives of any kind    it('can examine attached directives and listeners', () => {      const fixture = TestBed.createComponent(InputComponent);      fixture.detectChanges();      const inputEl = fixture.debugElement.query(By.css('input'));      expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel);      const ngControl = inputEl.injector.get(NgControl);      expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl));      expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2);    });    it('BankAccountComponent should set attributes, styles, classes, and properties', () => {      const fixture = TestBed.createComponent(BankAccountParentComponent);      fixture.detectChanges();      const comp = fixture.componentInstance;      // the only child is debugElement of the BankAccount component      const el = fixture.debugElement.children[0];      const childComp = el.componentInstance as BankAccountComponent;      expect(childComp).toEqual(jasmine.any(BankAccountComponent));      expect(el.context).withContext('context is the child component').toBe(childComp);      expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id);      expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank);      expect(el.classes['closed']).withContext('closed class').toBe(true);      expect(el.classes['open']).withContext('open class').toBeFalsy();      expect(el.styles['color']).withContext('color style').toBe(comp.color);      expect(el.styles['width'])        .withContext('width style')        .toBe(comp.width + 'px');      // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future?      // expect(el.properties['customProperty']).toBe(true, 'customProperty');    });  });  describe('TestBed component overrides:', () => {    it("should override ChildComp's template", () => {      const fixture = TestBed.configureTestingModule({        imports: [Child1Component],      })        .overrideComponent(Child1Component, {          set: {template: '<span>Fake</span>'},        })        .createComponent(Child1Component);      fixture.detectChanges();      expect(fixture).toHaveText('Fake');    });    it("should override TestProvidersComp's ValueService provider", () => {      const fixture = TestBed.configureTestingModule({        imports: [TestProvidersComponent],      })        .overrideComponent(TestProvidersComponent, {          remove: {providers: [ValueService]},          add: {providers: [{provide: ValueService, useClass: FakeValueService}]},          // Or replace them all (this component has only one provider)          // set:    { providers: [{ provide: ValueService, useClass: FakeValueService }] },        })        .createComponent(TestProvidersComponent);      fixture.detectChanges();      expect(fixture).toHaveText('injected value: faked value', 'text');      // Explore the providerTokens      const tokens = fixture.debugElement.providerTokens;      expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor);      expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent);      expect(tokens).withContext('ValueService').toContain(ValueService);    });    it("should override TestViewProvidersComp's ValueService viewProvider", () => {      const fixture = TestBed.configureTestingModule({        imports: [TestViewProvidersComponent],      })        .overrideComponent(TestViewProvidersComponent, {          // remove: { viewProviders: [ValueService]},          // add:    { viewProviders: [{ provide: ValueService, useClass: FakeValueService }]          // },          // Or replace them all (this component has only one viewProvider)          set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]},        })        .createComponent(TestViewProvidersComponent);      fixture.detectChanges();      expect(fixture).toHaveText('injected value: faked value');    });    it("injected provider should not be same as component's provider", () => {      // TestComponent is parent of TestProvidersComponent      @Component({        template: '<my-service-comp></my-service-comp>',        imports: [TestProvidersComponent],      })      class TestComponent {}      // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent      const fixture = TestBed.configureTestingModule({        imports: [TestComponent, TestProvidersComponent],        providers: [ValueService],      })        .overrideComponent(TestComponent, {          set: {providers: [{provide: ValueService, useValue: {}}]},        })        .overrideComponent(TestProvidersComponent, {          set: {providers: [{provide: ValueService, useClass: FakeValueService}]},        })        .createComponent(TestComponent);      let testBedProvider!: ValueService;      // `inject` uses TestBed's injector      inject([ValueService], (s: ValueService) => (testBedProvider = s))();      const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService;      const tpcProvider = fixture.debugElement.children[0].injector.get(        ValueService,      ) as FakeValueService;      expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider);      expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider);      expect(testBedProvider instanceof ValueService)        .withContext('testBedProvider is ValueService')        .toBe(true);      expect(tcProvider)        .withContext('tcProvider is {}')        .toEqual({} as ValueService);      expect(tpcProvider instanceof FakeValueService)        .withContext('tpcProvider is FakeValueService')        .toBe(true);    });    it('can access template local variables as references', () => {      const fixture = TestBed.configureTestingModule({        imports: [          ShellComponent,          NeedsContentComponent,          Child1Component,          Child2Component,          Child3Component,        ],      })        .overrideComponent(ShellComponent, {          set: {            selector: 'test-shell',            imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component],            template: `          <needs-content #nc>            <child-1 #content text="My"></child-1>            <child-2 #content text="dog"></child-2>            <child-2 text="has"></child-2>            <child-3 #content text="fleas"></child-3>            <div #content>!</div>          </needs-content>          `,          },        })        .createComponent(ShellComponent);      fixture.detectChanges();      // NeedsContentComp is the child of ShellComp      const el = fixture.debugElement.children[0];      const comp = el.componentInstance;      expect(comp.children.toArray().length)        .withContext('three different child components and an ElementRef with #content')        .toBe(4);      expect(el.references['nc']).withContext('#nc reference to component').toBe(comp);      // Filter for DebugElements with a #content reference      const contentRefs = el.queryAll((de) => de.references['content']);      expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4);    });  });  describe('nested (one-deep) component override', () => {    beforeEach(() => {      TestBed.configureTestingModule({        imports: [ParentComponent, FakeChildComponent],      }).overrideComponent(ParentComponent, {        set: {imports: [FakeChildComponent]},      });    });    it('ParentComp should use Fake Child component', () => {      const fixture = TestBed.createComponent(ParentComponent);      fixture.detectChanges();      expect(fixture).toHaveText('Parent(Fake Child)');    });  });  describe('nested (two-deep) component override', () => {    beforeEach(() => {      TestBed.configureTestingModule({        imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent],      }).overrideComponent(ParentComponent, {        set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]},      });    });    it('should use Fake Grandchild component', () => {      const fixture = TestBed.createComponent(ParentComponent);      fixture.detectChanges();      expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))');    });  });});////////// Fakes ///////////@Component({  selector: 'child-1',  template: 'Fake Child',})class FakeChildComponent {}@Component({  selector: 'grandchild-1',  template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({  selector: 'child-1',  imports: [FakeGrandchildComponent],  template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService {  override value = 'faked value';}

beforeEach() を使用しないテスト

このガイドのほとんどのテストスイートでは、beforeEach() を呼び出して各 it() テストの前提条件を設定し、TestBed にクラスの作成とサービスの注入を任せています。

beforeEach() を呼び出さない、別のテストの考え方があり、TestBed を使用せず、クラスを明示的に作成することを好みます。

このスタイルで MasterService のテストの1つを書き直す方法を以下に示します。

最初に、セットアップ 関数に、再利用可能な準備コードを beforeEach() の代わりに配置します。

app/demo/demo.spec.ts (setup)

      
import {LightswitchComponent, MasterService, ValueService, ReversePipe} from './demo';///////// Fakes /////////export class FakeValueService extends ValueService {  override value = 'faked service value';}////////////////////////describe('demo (no TestBed):', () => {  // Straight Jasmine testing without Angular's testing support  describe('ValueService', () => {    let service: ValueService;    beforeEach(() => {      service = new ValueService();    });    it('#getValue should return real value', () => {      expect(service.getValue()).toBe('real value');    });    it('#getObservableValue should return value from observable', (done: DoneFn) => {      service.getObservableValue().subscribe((value) => {        expect(value).toBe('observable value');        done();      });    });    it('#getPromiseValue should return value from a promise', (done: DoneFn) => {      service.getPromiseValue().then((value) => {        expect(value).toBe('promise value');        done();      });    });  });  // MasterService requires injection of a ValueService  describe('MasterService without Angular testing support', () => {    let masterService: MasterService;    it('#getValue should return real value from the real service', () => {      masterService = new MasterService(new ValueService());      expect(masterService.getValue()).toBe('real value');    });    it('#getValue should return faked value from a fakeService', () => {      masterService = new MasterService(new FakeValueService());      expect(masterService.getValue()).toBe('faked service value');    });    it('#getValue should return faked value from a fake object', () => {      const fake = {getValue: () => 'fake value'};      masterService = new MasterService(fake as ValueService);      expect(masterService.getValue()).toBe('fake value');    });    it('#getValue should return stubbed value from a spy', () => {      // create `getValue` spy on an object representing the ValueService      const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);      // set the value to return when the `getValue` spy is called.      const stubValue = 'stub value';      valueServiceSpy.getValue.and.returnValue(stubValue);      masterService = new MasterService(valueServiceSpy);      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });  });  describe('MasterService (no beforeEach)', () => {    it('#getValue should return stubbed value from a spy', () => {      const {masterService, stubValue, valueServiceSpy} = setup();      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });    function setup() {      const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);      const stubValue = 'stub value';      const masterService = new MasterService(valueServiceSpy);      valueServiceSpy.getValue.and.returnValue(stubValue);      return {masterService, stubValue, valueServiceSpy};    }  });  describe('ReversePipe', () => {    let pipe: ReversePipe;    beforeEach(() => {      pipe = new ReversePipe();    });    it('transforms "abc" to "cba"', () => {      expect(pipe.transform('abc')).toBe('cba');    });    it('no change to palindrome: "able was I ere I saw elba"', () => {      const palindrome = 'able was I ere I saw elba';      expect(pipe.transform(palindrome)).toBe(palindrome);    });  });  describe('LightswitchComp', () => {    it('#clicked() should toggle #isOn', () => {      const comp = new LightswitchComponent();      expect(comp.isOn).withContext('off at first').toBe(false);      comp.clicked();      expect(comp.isOn).withContext('on after click').toBe(true);      comp.clicked();      expect(comp.isOn).withContext('off after second click').toBe(false);    });    it('#clicked() should set #message to "is on"', () => {      const comp = new LightswitchComponent();      expect(comp.message)        .withContext('off at first')        .toMatch(/is off/i);      comp.clicked();      expect(comp.message).withContext('on after clicked').toMatch(/is on/i);    });  });});

setup() 関数は、テストで参照できる可能性のある変数 masterService などの変数を、オブジェクトリテラルとして返します。 describe() の本文には、半グローバル 変数(例:let masterService: MasterService)は定義しません。

次に、各テストは、テスト対象の操作や期待の主張を続行する前に、最初の行で setup() を呼び出します。

      
import {LightswitchComponent, MasterService, ValueService, ReversePipe} from './demo';///////// Fakes /////////export class FakeValueService extends ValueService {  override value = 'faked service value';}////////////////////////describe('demo (no TestBed):', () => {  // Straight Jasmine testing without Angular's testing support  describe('ValueService', () => {    let service: ValueService;    beforeEach(() => {      service = new ValueService();    });    it('#getValue should return real value', () => {      expect(service.getValue()).toBe('real value');    });    it('#getObservableValue should return value from observable', (done: DoneFn) => {      service.getObservableValue().subscribe((value) => {        expect(value).toBe('observable value');        done();      });    });    it('#getPromiseValue should return value from a promise', (done: DoneFn) => {      service.getPromiseValue().then((value) => {        expect(value).toBe('promise value');        done();      });    });  });  // MasterService requires injection of a ValueService  describe('MasterService without Angular testing support', () => {    let masterService: MasterService;    it('#getValue should return real value from the real service', () => {      masterService = new MasterService(new ValueService());      expect(masterService.getValue()).toBe('real value');    });    it('#getValue should return faked value from a fakeService', () => {      masterService = new MasterService(new FakeValueService());      expect(masterService.getValue()).toBe('faked service value');    });    it('#getValue should return faked value from a fake object', () => {      const fake = {getValue: () => 'fake value'};      masterService = new MasterService(fake as ValueService);      expect(masterService.getValue()).toBe('fake value');    });    it('#getValue should return stubbed value from a spy', () => {      // create `getValue` spy on an object representing the ValueService      const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);      // set the value to return when the `getValue` spy is called.      const stubValue = 'stub value';      valueServiceSpy.getValue.and.returnValue(stubValue);      masterService = new MasterService(valueServiceSpy);      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });  });  describe('MasterService (no beforeEach)', () => {    it('#getValue should return stubbed value from a spy', () => {      const {masterService, stubValue, valueServiceSpy} = setup();      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });    function setup() {      const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);      const stubValue = 'stub value';      const masterService = new MasterService(valueServiceSpy);      valueServiceSpy.getValue.and.returnValue(stubValue);      return {masterService, stubValue, valueServiceSpy};    }  });  describe('ReversePipe', () => {    let pipe: ReversePipe;    beforeEach(() => {      pipe = new ReversePipe();    });    it('transforms "abc" to "cba"', () => {      expect(pipe.transform('abc')).toBe('cba');    });    it('no change to palindrome: "able was I ere I saw elba"', () => {      const palindrome = 'able was I ere I saw elba';      expect(pipe.transform(palindrome)).toBe(palindrome);    });  });  describe('LightswitchComp', () => {    it('#clicked() should toggle #isOn', () => {      const comp = new LightswitchComponent();      expect(comp.isOn).withContext('off at first').toBe(false);      comp.clicked();      expect(comp.isOn).withContext('on after click').toBe(true);      comp.clicked();      expect(comp.isOn).withContext('off after second click').toBe(false);    });    it('#clicked() should set #message to "is on"', () => {      const comp = new LightswitchComponent();      expect(comp.message)        .withContext('off at first')        .toMatch(/is off/i);      comp.clicked();      expect(comp.message).withContext('on after clicked').toMatch(/is on/i);    });  });});

テストで デストラクチャリング代入 を使用して、必要なセットアップ変数を抽出したことに注意してください。

      
import {LightswitchComponent, MasterService, ValueService, ReversePipe} from './demo';///////// Fakes /////////export class FakeValueService extends ValueService {  override value = 'faked service value';}////////////////////////describe('demo (no TestBed):', () => {  // Straight Jasmine testing without Angular's testing support  describe('ValueService', () => {    let service: ValueService;    beforeEach(() => {      service = new ValueService();    });    it('#getValue should return real value', () => {      expect(service.getValue()).toBe('real value');    });    it('#getObservableValue should return value from observable', (done: DoneFn) => {      service.getObservableValue().subscribe((value) => {        expect(value).toBe('observable value');        done();      });    });    it('#getPromiseValue should return value from a promise', (done: DoneFn) => {      service.getPromiseValue().then((value) => {        expect(value).toBe('promise value');        done();      });    });  });  // MasterService requires injection of a ValueService  describe('MasterService without Angular testing support', () => {    let masterService: MasterService;    it('#getValue should return real value from the real service', () => {      masterService = new MasterService(new ValueService());      expect(masterService.getValue()).toBe('real value');    });    it('#getValue should return faked value from a fakeService', () => {      masterService = new MasterService(new FakeValueService());      expect(masterService.getValue()).toBe('faked service value');    });    it('#getValue should return faked value from a fake object', () => {      const fake = {getValue: () => 'fake value'};      masterService = new MasterService(fake as ValueService);      expect(masterService.getValue()).toBe('fake value');    });    it('#getValue should return stubbed value from a spy', () => {      // create `getValue` spy on an object representing the ValueService      const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);      // set the value to return when the `getValue` spy is called.      const stubValue = 'stub value';      valueServiceSpy.getValue.and.returnValue(stubValue);      masterService = new MasterService(valueServiceSpy);      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });  });  describe('MasterService (no beforeEach)', () => {    it('#getValue should return stubbed value from a spy', () => {      const {masterService, stubValue, valueServiceSpy} = setup();      expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);      expect(valueServiceSpy.getValue.calls.count())        .withContext('spy method was called once')        .toBe(1);      expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);    });    function setup() {      const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);      const stubValue = 'stub value';      const masterService = new MasterService(valueServiceSpy);      valueServiceSpy.getValue.and.returnValue(stubValue);      return {masterService, stubValue, valueServiceSpy};    }  });  describe('ReversePipe', () => {    let pipe: ReversePipe;    beforeEach(() => {      pipe = new ReversePipe();    });    it('transforms "abc" to "cba"', () => {      expect(pipe.transform('abc')).toBe('cba');    });    it('no change to palindrome: "able was I ere I saw elba"', () => {      const palindrome = 'able was I ere I saw elba';      expect(pipe.transform(palindrome)).toBe(palindrome);    });  });  describe('LightswitchComp', () => {    it('#clicked() should toggle #isOn', () => {      const comp = new LightswitchComponent();      expect(comp.isOn).withContext('off at first').toBe(false);      comp.clicked();      expect(comp.isOn).withContext('on after click').toBe(true);      comp.clicked();      expect(comp.isOn).withContext('off after second click').toBe(false);    });    it('#clicked() should set #message to "is on"', () => {      const comp = new LightswitchComponent();      expect(comp.message)        .withContext('off at first')        .toMatch(/is off/i);      comp.clicked();      expect(comp.message).withContext('on after clicked').toMatch(/is on/i);    });  });});

多くの開発者は、このアプローチは従来の beforeEach() スタイルよりもクリーンで明示的だと感じるでしょう。

このテストガイドでは、従来のスタイルと、デフォルトの CLI スキーマbeforeEach()TestBed を使用してテストファイルが生成されますが、独自のプロジェクトで この代替アプローチ を採用することは自由です。

HTTP サービスのテスト

リモートサーバーにHTTP呼び出しするデータサービスは、通常、Angularの HttpClient サービスを注入して委譲し、XHRを呼び出します。

依存関係が注入された HttpClient スパイを使用して、データサービスをテストできます。

app/model/hero.service.spec.ts (スパイを使用したテスト)

      
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';// Other importsimport {TestBed} from '@angular/core/testing';import {HttpClient, HttpResponse, HttpErrorResponse} from '@angular/common/http';import {asyncData, asyncError} from '../../testing/async-observable-helpers';import {Hero} from './hero';import {HeroService} from './hero.service';describe('HeroesService (with spies)', () => {  let httpClientSpy: jasmine.SpyObj<HttpClient>;  let heroService: HeroService;  beforeEach(() => {    // TODO: spy on other methods too    httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);    heroService = new HeroService(httpClientSpy);  });  it('should return expected heroes (HttpClient called once)', (done: DoneFn) => {    const expectedHeroes: Hero[] = [      {id: 1, name: 'A'},      {id: 2, name: 'B'},    ];    httpClientSpy.get.and.returnValue(asyncData(expectedHeroes));    heroService.getHeroes().subscribe({      next: (heroes) => {        expect(heroes).withContext('expected heroes').toEqual(expectedHeroes);        done();      },      error: done.fail,    });    expect(httpClientSpy.get.calls.count()).withContext('one call').toBe(1);  });  it('should return an error when the server returns a 404', (done: DoneFn) => {    const errorResponse = new HttpErrorResponse({      error: 'test 404 error',      status: 404,      statusText: 'Not Found',    });    httpClientSpy.get.and.returnValue(asyncError(errorResponse));    heroService.getHeroes().subscribe({      next: (heroes) => done.fail('expected an error, not heroes'),      error: (error) => {        expect(error.message).toContain('test 404 error');        done();      },    });  });});describe('HeroesService (with mocks)', () => {  let httpClient: HttpClient;  let httpTestingController: HttpTestingController;  let heroService: HeroService;  beforeEach(() => {    TestBed.configureTestingModule({      // Import the HttpClient mocking services      imports: [HttpClientTestingModule],      // Provide the service-under-test      providers: [HeroService],    });    // Inject the http, test controller, and service-under-test    // as they will be referenced by each test.    httpClient = TestBed.inject(HttpClient);    httpTestingController = TestBed.inject(HttpTestingController);    heroService = TestBed.inject(HeroService);  });  afterEach(() => {    // After every test, assert that there are no more pending requests.    httpTestingController.verify();  });  /// HeroService method tests begin ///  describe('#getHeroes', () => {    let expectedHeroes: Hero[];    beforeEach(() => {      heroService = TestBed.inject(HeroService);      expectedHeroes = [        {id: 1, name: 'A'},        {id: 2, name: 'B'},      ] as Hero[];    });    it('should return expected heroes (called once)', () => {      heroService.getHeroes().subscribe({        next: (heroes) =>          expect(heroes).withContext('should return expected heroes').toEqual(expectedHeroes),        error: fail,      });      // HeroService should have made one request to GET heroes from expected URL      const req = httpTestingController.expectOne(heroService.heroesUrl);      expect(req.request.method).toEqual('GET');      // Respond with the mock heroes      req.flush(expectedHeroes);    });    it('should be OK returning no heroes', () => {      heroService.getHeroes().subscribe({        next: (heroes) =>          expect(heroes.length).withContext('should have empty heroes array').toEqual(0),        error: fail,      });      const req = httpTestingController.expectOne(heroService.heroesUrl);      req.flush([]); // Respond with no heroes    });    it('should turn 404 into a user-friendly error', () => {      const msg = 'Deliberate 404';      heroService.getHeroes().subscribe({        next: (heroes) => fail('expected to fail'),        error: (error) => expect(error.message).toContain(msg),      });      const req = httpTestingController.expectOne(heroService.heroesUrl);      // respond with a 404 and the error message in the body      req.flush(msg, {status: 404, statusText: 'Not Found'});    });    it('should return expected heroes (called multiple times)', () => {      heroService.getHeroes().subscribe();      heroService.getHeroes().subscribe();      heroService.getHeroes().subscribe({        next: (heroes) =>          expect(heroes).withContext('should return expected heroes').toEqual(expectedHeroes),        error: fail,      });      const requests = httpTestingController.match(heroService.heroesUrl);      expect(requests.length).withContext('calls to getHeroes()').toEqual(3);      // Respond to each request with different mock hero results      requests[0].flush([]);      requests[1].flush([{id: 1, name: 'bob'}]);      requests[2].flush(expectedHeroes);    });  });  describe('#updateHero', () => {    // Expecting the query form of URL so should not 404 when id not found    const makeUrl = (id: number) => `${heroService.heroesUrl}/?id=${id}`;    it('should update a hero and return it', () => {      const updateHero: Hero = {id: 1, name: 'A'};      heroService.updateHero(updateHero).subscribe({        next: (data) => expect(data).withContext('should return the hero').toEqual(updateHero),        error: fail,      });      // HeroService should have made one request to PUT hero      const req = httpTestingController.expectOne(heroService.heroesUrl);      expect(req.request.method).toEqual('PUT');      expect(req.request.body).toEqual(updateHero);      // Expect server to return the hero after PUT      const expectedResponse = new HttpResponse({        status: 200,        statusText: 'OK',        body: updateHero,      });      req.event(expectedResponse);    });    it('should turn 404 error into user-facing error', () => {      const msg = 'Deliberate 404';      const updateHero: Hero = {id: 1, name: 'A'};      heroService.updateHero(updateHero).subscribe({        next: (heroes) => fail('expected to fail'),        error: (error) => expect(error.message).toContain(msg),      });      const req = httpTestingController.expectOne(heroService.heroesUrl);      // respond with a 404 and the error message in the body      req.flush(msg, {status: 404, statusText: 'Not Found'});    });    it('should turn network error into user-facing error', (done) => {      // Create mock ProgressEvent with type `error`, raised when something goes wrong at      // the network level. Connection timeout, DNS error, offline, etc.      const errorEvent = new ProgressEvent('error');      const updateHero: Hero = {id: 1, name: 'A'};      heroService.updateHero(updateHero).subscribe({        next: (heroes) => fail('expected to fail'),        error: (error) => {          expect(error).toBe(errorEvent);          done();        },      });      const req = httpTestingController.expectOne(heroService.heroesUrl);      // Respond with mock error      req.error(errorEvent);    });  });  // TODO: test other HeroService methods});

IMPORTANT: HeroService メソッドは Observable を返します。 Observableに 登録 することで、(a) 実行させ、(b) メソッドが成功したか失敗したかをアサートする必要があります。

subscribe() メソッドは、成功 (next) と失敗 (error) のコールバックを受け取ります。 エラーを捕捉するために、両方の コールバックを提供してください。 これを怠ると、非同期でキャッチされないObservableエラーが発生し、テストランナーは別のテストによるエラーであると判断する可能性があります。

HttpClientTestingModule

データサービスと HttpClient の間の拡張されたやり取りは複雑で、スパイでモック化するのは難しい場合があります。

HttpClientTestingModule を使用すると、これらのテストシナリオをより管理しやすくなります。

このガイドに付属する コードサンプル では HttpClientTestingModule が示されていますが、このページでは、HttpClientTestingModule を使用したテストを詳しく説明している HTTP ガイド を参照します。