In-depth Guides
Testing

Testing Utility APIs

This page describes the most useful Angular testing features.

The Angular testing utilities include the TestBed, the ComponentFixture, and a handful of functions that control the test environment. The TestBed and ComponentFixture classes are covered separately.

Here's a summary of the stand-alone functions, in order of likely utility:

Function Details
waitForAsync Runs the body of a test (it) or setup (beforeEach) function within a special async test zone. See waitForAsync.
fakeAsync Runs the body of a test (it) within a special fakeAsync test zone, enabling a linear control flow coding style. See fakeAsync.
tick Simulates the passage of time and the completion of pending asynchronous activities by flushing both timer and micro-task queues within the fakeAsync test zone. The curious, dedicated reader might enjoy this lengthy blog post, "Tasks, microtasks, queues and schedules". Accepts an optional argument that moves the virtual clock forward by the specified number of milliseconds, clearing asynchronous activities scheduled within that timeframe. See tick.
inject Injects one or more services from the current TestBed injector into a test function. It cannot inject a service provided by the component itself. See discussion of the debugElement.injector.
discardPeriodicTasks When a fakeAsync() test ends with pending timer event tasks (queued setTimeOut and setInterval callbacks), the test fails with a clear error message.
In general, a test should end with no queued tasks. When pending timer tasks are expected, call discardPeriodicTasks to flush the task queue and avoid the error.
flushMicrotasks When a fakeAsync() test ends with pending micro-tasks such as unresolved promises, the test fails with a clear error message.
In general, a test should wait for micro-tasks to finish. When pending microtasks are expected, call flushMicrotasks to flush the micro-task queue and avoid the error.
ComponentFixtureAutoDetect A provider token for a service that turns on automatic change detection.
getTestBed Gets the current instance of the TestBed. Usually unnecessary because the static class methods of the TestBed class are typically sufficient. The TestBed instance exposes a few rarely used members that are not available as static methods.

TestBed class summary

The TestBed class is one of the principal Angular testing utilities. Its API is quite large and can be overwhelming until you've explored it, a little at a time. Read the early part of this guide first to get the basics before trying to absorb the full API.

The module definition passed to configureTestingModule is a subset of the @NgModule metadata properties.

      
type TestModuleMetadata = {  providers?: any[];  declarations?: any[];  imports?: any[];  schemas?: Array<SchemaMetadata | any[]>;};

Each override method takes a MetadataOverride<T> where T is the kind of metadata appropriate to the method, that is, the parameter of an @NgModule, @Component, @Directive, or @Pipe.

      
type MetadataOverride<T> = {  add?: Partial<T>;  remove?: Partial<T>;  set?: Partial<T>;};

The TestBed API consists of static class methods that either update or reference a global instance of the TestBed.

Internally, all static methods cover methods of the current runtime TestBed instance, which is also returned by the getTestBed() function.

Call TestBed methods within a beforeEach() to ensure a fresh start before each individual test.

Here are the most important static methods, in order of likely utility.

Methods Details
configureTestingModule The testing shims (karma-test-shim, browser-test-shim) establish the initial test environment and a default testing module. The default testing module is configured with basic declaratives and some Angular service substitutes that every tester needs.
Call configureTestingModule to refine the testing module configuration for a particular set of tests by adding and removing imports, declarations (of components, directives, and pipes), and providers.
compileComponents Compile the testing module asynchronously after you've finished configuring it. You must call this method if any of the testing module components have a templateUrl or styleUrls because fetching component template and style files is necessarily asynchronous. See compileComponents.
After calling compileComponents, the TestBed configuration is frozen for the duration of the current spec.
createComponent<T> Create an instance of a component of type T based on the current TestBed configuration. After calling createComponent, the TestBed configuration is frozen for the duration of the current spec.
overrideModule Replace metadata for the given NgModule. Recall that modules can import other modules. The overrideModule method can reach deeply into the current testing module to modify one of these inner modules.
overrideComponent Replace metadata for the given component class, which could be nested deeply within an inner module.
overrideDirective Replace metadata for the given directive class, which could be nested deeply within an inner module.
overridePipe Replace metadata for the given pipe class, which could be nested deeply within an inner module.
inject Retrieve a service from the current TestBed injector. The inject function is often adequate for this purpose. But inject throws an error if it can't provide the service.
What if the service is optional?
The TestBed.inject() method takes an optional second parameter, the object to return if Angular can't find the provider (null in this example): After calling TestBed.inject, the TestBed configuration is frozen for the duration of the current spec.
initTestEnvironment Initialize the testing environment for the entire test run.
The testing shims (karma-test-shim, browser-test-shim) call it for you so there is rarely a reason for you to call it yourself.
Call this method exactly once. To change this default in the middle of a test run, call resetTestEnvironment first.
Specify the Angular compiler factory, a PlatformRef, and a default Angular testing module. Alternatives for non-browser platforms are available in the general form @angular/platform-<platform_name>/testing/<platform_name>.
resetTestEnvironment Reset the initial test environment, including the default testing module.

A few of the TestBed instance methods are not covered by static TestBed class methods. These are rarely needed.

The ComponentFixture

The TestBed.createComponent<T> creates an instance of the component T and returns a strongly typed ComponentFixture for that component.

The ComponentFixture properties and methods provide access to the component, its DOM representation, and aspects of its Angular environment.

ComponentFixture properties

Here are the most important properties for testers, in order of likely utility.

Properties Details
componentInstance The instance of the component class created by TestBed.createComponent.
debugElement The DebugElement associated with the root element of the component.
The debugElement provides insight into the component and its DOM element during test and debugging. It's a critical property for testers. The most interesting members are covered below.
nativeElement The native DOM element at the root of the component.
changeDetectorRef The ChangeDetectorRef for the component.
The ChangeDetectorRef is most valuable when testing a component that has the ChangeDetectionStrategy.OnPush method or the component's change detection is under your programmatic control.

ComponentFixture methods

The fixture methods cause Angular to perform certain tasks on the component tree. Call these method to trigger Angular behavior in response to simulated user action.

Here are the most useful methods for testers.

Methods Details
detectChanges Trigger a change detection cycle for the component.
Call it to initialize the component (it calls ngOnInit) and after your test code, change the component's data bound property values. Angular can't see that you've changed personComponent.name and won't update the name binding until you call detectChanges.
Runs checkNoChanges afterwards to confirm that there are no circular updates unless called as detectChanges(false);
autoDetectChanges Set this to true when you want the fixture to detect changes automatically.
When autodetect is true, the test fixture calls detectChanges immediately after creating the component. Then it listens for pertinent zone events and calls detectChanges accordingly. When your test code modifies component property values directly, you probably still have to call fixture.detectChanges to trigger data binding updates.
The default is false. Testers who prefer fine control over test behavior tend to keep it false.
checkNoChanges Do a change detection run to make sure there are no pending changes. Throws an exceptions if there are.
isStable If the fixture is currently stable, returns true. If there are async tasks that have not completed, returns false.
whenStable Returns a promise that resolves when the fixture is stable.
To resume testing after completion of asynchronous activity or asynchronous change detection, hook that promise. See whenStable.
destroy Trigger component destruction.

DebugElement

The DebugElement provides crucial insights into the component's DOM representation.

From the test root component's DebugElement returned by fixture.debugElement, you can walk (and query) the fixture's entire element and component subtrees.

Here are the most useful DebugElement members for testers, in approximate order of utility:

Members Details
nativeElement The corresponding DOM element in the browser
query Calling query(predicate: Predicate<DebugElement>) returns the first DebugElement that matches the predicate at any depth in the subtree.
queryAll Calling queryAll(predicate: Predicate<DebugElement>) returns all DebugElements that matches the predicate at any depth in subtree.
injector The host dependency injector. For example, the root element's component instance injector.
componentInstance The element's own component instance, if it has one.
context An object that provides parent context for this element. Often an ancestor component instance that governs this element.
When an element is repeated within *ngFor, the context is an NgForOf whose $implicit property is the value of the row instance value. For example, the hero in *ngFor="let hero of heroes".
children The immediate DebugElement children. Walk the tree by descending through children. DebugElement also has childNodes, a list of DebugNode objects. DebugElement derives from DebugNode objects and there are often more nodes than elements. Testers can usually ignore plain nodes.
parent The DebugElement parent. Null if this is the root element.
name The element tag name, if it is an element.
triggerEventHandler Triggers the event by its name if there is a corresponding listener in the element's listeners collection. The second parameter is the event object expected by the handler. See triggerEventHandler.
If the event lacks a listener or there's some other problem, consider calling nativeElement.dispatchEvent(eventObject).
listeners The callbacks attached to the component's @Output properties and/or the element's event properties.
providerTokens This component's injector lookup tokens. Includes the component itself plus the tokens that the component lists in its providers metadata.
source Where to find this element in the source component template.
references Dictionary of objects associated with template local variables (for example, #foo), keyed by the local variable name.

The DebugElement.query(predicate) and DebugElement.queryAll(predicate) methods take a predicate that filters the source element's subtree for matching DebugElement.

The predicate is any method that takes a DebugElement and returns a truthy value. The following example finds all DebugElements with a reference to a template local variable named "content":

app/demo/demo.testbed.spec.ts

      
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    //     .compileComponents();    // }));    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))');    });  });  describe('lifecycle hooks w/ MyIfParentComp', () => {    let fixture: ComponentFixture<MyIfParentComponent>;    let parent: MyIfParentComponent;    let child: MyIfChildComponent;    beforeEach(() => {      TestBed.configureTestingModule({        imports: [FormsModule, MyIfChildComponent, MyIfParentComponent],      });      fixture = TestBed.createComponent(MyIfParentComponent);      parent = fixture.componentInstance;    });    it('should instantiate parent component', () => {      expect(parent).withContext('parent component should exist').not.toBeNull();    });    it('parent component OnInit should NOT be called before first detectChanges()', () => {      expect(parent.ngOnInitCalled).toBe(false);    });    it('parent component OnInit should be called after first detectChanges()', () => {      fixture.detectChanges();      expect(parent.ngOnInitCalled).toBe(true);    });    it('child component should exist after OnInit', () => {      fixture.detectChanges();      getChild();      expect(child instanceof MyIfChildComponent)        .withContext('should create child')        .toBe(true);    });    it("should have called child component's OnInit ", () => {      fixture.detectChanges();      getChild();      expect(child.ngOnInitCalled).toBe(true);    });    it('child component called OnChanges once', () => {      fixture.detectChanges();      getChild();      expect(child.ngOnChangesCounter).toBe(1);    });    it('changed parent value flows to child', () => {      fixture.detectChanges();      getChild();      parent.parentValue = 'foo';      fixture.detectChanges();      expect(child.ngOnChangesCounter)        .withContext('expected 2 changes: initial value and changed value')        .toBe(2);      expect(child.childValue).withContext('childValue should eq changed parent value').toBe('foo');    });    // must be async test to see child flow to parent    it('changed child value flows to parent', waitForAsync(() => {      fixture.detectChanges();      getChild();      child.childValue = 'bar';      return new Promise<void>((resolve) => {        // Wait one JS engine turn!        setTimeout(() => resolve(), 0);      }).then(() => {        fixture.detectChanges();        expect(child.ngOnChangesCounter)          .withContext('expected 2 changes: initial value and changed value')          .toBe(2);        expect(parent.parentValue)          .withContext('parentValue should eq changed parent value')          .toBe('bar');      });    }));    it('clicking "Close Child" triggers child OnDestroy', () => {      fixture.detectChanges();      getChild();      const btn = fixture.debugElement.query(By.css('button'));      click(btn);      fixture.detectChanges();      expect(child.ngOnDestroyCalled).toBe(true);    });    ////// helpers ///    /**     * Get the MyIfChildComp from parent; fail w/ good message if cannot.     */    function getChild() {      let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp      // The Hard Way: requires detailed knowledge of the parent template      try {        childDe = fixture.debugElement.children[4].children[0];      } catch (err) {        /* we'll report the error */      }      // DebugElement.queryAll: if we wanted all of many instances:      childDe = fixture.debugElement.queryAll(        (de) => de.componentInstance instanceof MyIfChildComponent,      )[0];      // WE'LL USE THIS APPROACH !      // DebugElement.query: find first instance (if any)      childDe = fixture.debugElement.query(        (de) => de.componentInstance instanceof MyIfChildComponent,      );      if (childDe && childDe.componentInstance) {        child = childDe.componentInstance;      } else {        fail('Unable to find MyIfChildComp within MyIfParentComp');      }      return child;    }  });});////////// 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';}

The Angular By class has three static methods for common predicates:

Static method Details
By.all Return all elements
By.css(selector) Return elements with matching CSS selectors
By.directive(directive) Return elements that Angular matched to an instance of the directive class

app/hero/hero-list.component.spec.ts

      
import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {DebugElement} from '@angular/core';import {Router} from '@angular/router';import {addMatchers} from '../../testing';import {HeroService} from '../model/hero.service';import {getTestHeroes, TestHeroService} from '../model/testing/test-hero.service';import {HeroListComponent} from './hero-list.component';import {HighlightDirective} from '../shared/highlight.directive';import {appConfig} from '../app.config';const HEROES = getTestHeroes();let comp: HeroListComponent;let fixture: ComponentFixture<HeroListComponent>;let page: Page;/////// Tests //////describe('HeroListComponent', () => {  beforeEach(waitForAsync(() => {    addMatchers();    const routerSpy = jasmine.createSpyObj('Router', ['navigate']);    TestBed.configureTestingModule(      Object.assign({}, appConfig, {        providers: [          {provide: HeroService, useClass: TestHeroService},          {provide: Router, useValue: routerSpy},        ],      }),    )      .compileComponents()      .then(createComponent);  }));  it('should display heroes', () => {    expect(page.heroRows.length).toBeGreaterThan(0);  });  it('1st hero should match 1st test hero', () => {    const expectedHero = HEROES[0];    const actualHero = page.heroRows[0].textContent;    expect(actualHero).withContext('hero.id').toContain(expectedHero.id.toString());    expect(actualHero).withContext('hero.name').toContain(expectedHero.name);  });  it('should select hero on click', fakeAsync(() => {    const expectedHero = HEROES[1];    const btn = page.heroRows[1].querySelector('button');    btn!.dispatchEvent(new Event('click'));    tick();    // `.toEqual` because selectedHero is clone of expectedHero; see FakeHeroService    expect(comp.selectedHero).toEqual(expectedHero);  }));  it('should navigate to selected hero detail on click', fakeAsync(() => {    const expectedHero = HEROES[1];    const btn = page.heroRows[1].querySelector('button');    btn!.dispatchEvent(new Event('click'));    tick();    // should have navigated    expect(page.navSpy.calls.any()).withContext('navigate called').toBe(true);    // composed hero detail will be URL like 'heroes/42'    // expect link array with the route path and hero id    // first argument to router.navigate is link array    const navArgs = page.navSpy.calls.first().args[0];    expect(navArgs[0]).withContext('nav to heroes detail URL').toContain('heroes');    expect(navArgs[1]).withContext('expected hero.id').toBe(expectedHero.id);  }));  it('should find `HighlightDirective` with `By.directive', () => {    // Can find DebugElement either by css selector or by directive    const h2 = fixture.debugElement.query(By.css('h2'));    const directive = fixture.debugElement.query(By.directive(HighlightDirective));    expect(h2).toBe(directive);  });  it('should color header with `HighlightDirective`', () => {    const h2 = page.highlightDe.nativeElement as HTMLElement;    const bgColor = h2.style.backgroundColor;    // different browsers report color values differently    const isExpectedColor = bgColor === 'gold' || bgColor === 'rgb(255, 215, 0)';    expect(isExpectedColor).withContext('backgroundColor').toBe(true);  });  it("the `HighlightDirective` is among the element's providers", () => {    expect(page.highlightDe.providerTokens)      .withContext('HighlightDirective')      .toContain(HighlightDirective);  });});/////////// Helpers //////** Create the component and set the `page` test variables */function createComponent() {  fixture = TestBed.createComponent(HeroListComponent);  comp = fixture.componentInstance;  // change detection triggers ngOnInit which gets a hero  fixture.detectChanges();  return fixture.whenStable().then(() => {    // got the heroes and updated component    // change detection updates the view    fixture.detectChanges();    page = new Page();  });}class Page {  /** Hero line elements */  heroRows: HTMLLIElement[];  /** Highlighted DebugElement */  highlightDe: DebugElement;  /** Spy on router navigate method */  navSpy: jasmine.Spy;  constructor() {    const heroRowNodes = fixture.nativeElement.querySelectorAll('li');    this.heroRows = Array.from(heroRowNodes);    // Find the first element with an attached HighlightDirective    this.highlightDe = fixture.debugElement.query(By.directive(HighlightDirective));    // Get the component's injected router navigation spy    const routerSpy = fixture.debugElement.injector.get(Router);    this.navSpy = routerSpy.navigate as jasmine.Spy;  }}