The Wayback Machine - https://web.archive.org/web/20161030025809/https://angular.io/docs/ts/latest/guide/testing.html

Testing

This chapter offers tips and techniques for testing Angular applications. Along the way you will learn some general testing principles and techniques but the focus is on testing applications written with Angular

Table of Contents

  1. Introduction to Angular Testing

  2. Setup
  3. The first karma test

  4. Introduction to the Angular testing utilities

  5. The sample application and its tests

  6. A simple component test
  7. Test a component with a service dependency
  8. Test a component with an async service
  9. Test a component with an external template
  10. Test a component with inputs and outputs
  11. Test a component inside a test host component

  12. Test a routed component
  13. Test a routed component with parameters
  14. Use a page object to simplify setup

  15. Setup with module imports

  16. Override component providers

  17. Test a RouterOutlet component
  18. "Shallow" component tests with NO_ERRORS_SCHEMA

  19. Test an attribute directive

  20. Isolated unit tests
  21. Angular testing utility APIs
  22. FAQ

It’s a big agenda. Fortunately, you can learn a little bit at a time and put each lesson to use.

Live examples

The chapter sample code is available as live examples for inspection, experiment, and download.

Back to top

Introduction to Angular Testing

You write tests to explore and confirm the behavior of the application.

  1. They guard against changes that break existing code (“regressions”).

  2. They clarify what the code does both when used as intended and when faced with deviant conditions.

  3. They reveal mistakes in design and implementation. Tests shine a harsh light on the code from many angles. When a part of the application seems hard to test, the root cause is often a design flaw, something to cure now rather than later when it becomes expensive to fix.

Tools and Technologies

You can write and run Angular tests with a variety of tools and technologies. This chapter describes specific choices that are known to work well.

TechnologyPurpose
Jasmine

The Jasmine test framework. provides everything needed to write basic tests. It ships with an HTML test runner that executes tests in the browser.

Angular Testing Utilities

The Angular testing utilities create a test environment for the Angular application code under test. Use them to condition and control parts of the application as they interact within the Angular environment.

Karma

The karma test runner is ideal for writing and running unit tests while developing the application. It can be an integral part of the project's development and continuous integration processes. This chapter describes how to setup and run tests with karma.

Protractor

Use protractor to write and run end-to-end (e2e) tests. End-to-end tests explore the application as users experience it. In e2e testing, one process runs the real application and a second process runs protractor tests that simulate user behavior and assert that the application responds in the browser as expected.

Setup

Many think writing tests is fun. Few enjoy setting up the test environment. To get to the fun as quickly as possible, the deep details of setup appear later in the chapter (forthcoming). A bare minimum of discussion plus the downloadable source code must suffice for now.

There are two fast paths to getting started.

  1. Start a new project following the instructions in the QuickStart github repository.

  2. Start a new project with the Angular CLI.

Both approaches install npm packages, files, and scripts pre-configured for applications built in their respective modalities. Their artifacts and procedures differ slightly but their essentials are the same and there are no differences in the test code.

In this chapter, the application and its tests are based on the QuickStart repo.

If your application was based on the QuickStart repository, you can skip the rest of this section and get on with your first test. The QuickStart repo provides all necessary setup.

Setup files

Here's brief description of the setup files.

FileDescription
karma.conf.js

The karma configuration file that specifies which plug-ins to use, which application and test files to load, which browser(s) to use, and how to report test results.

It loads three other setup files:

  • systemjs.config.js
  • systemjs.config.extras.js
  • karma-test-shim.js
karma-test-shim.js

This shim prepares karma specifically for the Angular test environment and launches karma itself. It loads the systemjs.config.js file as part of that process.

systemjs.config.js

SystemJS loads the application and test files. This script tells SystemJS where to find those files and how to load them. It's the same version of systemjs.config.js used by QuickStart-based applications.

systemjs.config.extras.js

An optional file that supplements the SystemJS configuration in systemjs.config.js with configuration for the specific needs of the application itself.

A stock systemjs.config.js can't anticipate those needs. You fill the gaps here.

The sample version for this chapter adds the model barrel to the SystemJs packages configuration.

systemjs.config.extras.js

/** App specific SystemJS configuration */ System.config({ packages: { // barrels 'app/model': {main:'index.js', defaultExtension:'js'}, 'app/model/testing': {main:'index.js', defaultExtension:'js'} } });

npm packages

The sample tests are written to run in Jasmine and karma. The two "fast path" setups added the appropriate Jasmine and karma npm packages to the devDependencies section of the package.json. They were installed when you ran npm install.

The first karma test

Start with a simple test to make sure the setup works properly.

Create a new file called 1st.spec.ts in the application root folder, app/

Tests written in Jasmine are called specs . The filename extension must be .spec.ts, the convention adhered to by karma.conf.js and other tooling.

Put spec files somewhere within the app/ folder. The karma.conf.js tells karma to look for spec files there, for reasons explained below.

Add the following code to app/1st.spec.ts.

app/1st.spec.ts

describe('1st tests', () => { it('true is true', () => expect(true).toBe(true)); });

Run karma

Compile and run it in karma from the command line with this command:

npm test

The command compiles the application and test code and starts karma. Both processes watch pertinent files, write messages to the console, and re-run when they detect changes.

The QuickStart development path defined the test command in the scripts section of npm's package.json. The Angular CLI has different commands to do the same thing. Adjust accordingly.

After a few moments, karma opens a browser and starts writing to the console.

Karma browser

Hide (don't close!) the browser and focus on the console output which should look something like this.

> npm test ... [0] 1:37:03 PM - Compilation complete. Watching for file changes. ... [1] Chrome 51.0.2704: Executed 0 of 0 SUCCESS Chrome 51.0.2704: Executed 1 of 1 SUCCESS SUCCESS (0.005 secs / 0.005 secs)

Both the compiler and karma continue to run. The compiler output is preceeded by [0]; the karma output by [1].

Change the expectation from true to false.

The compiler watcher detects the change and recompiles.

[0] 1:49:21 PM - File change detected. Starting incremental compilation... [0] 1:49:25 PM - Compilation complete. Watching for file changes.

The karma watcher detects the change to the compilation output and re-runs the test.

[1] Chrome 51.0.2704 1st tests true is true FAILED [1] Expected false to equal true. [1] Chrome 51.0.2704: Executed 1 of 1 (1 FAILED) (0.005 secs / 0.005 secs)

It failed of course.

Restore the expectation from false back to true. Both processes detect the change, re-run, and karma reports complete success.

The console log can be quite long. Keep your eye on the last line. It says SUCCESS when all is well.

Test debugging

Debug specs in the browser in the same way you debug an application.

Karma debugging
Back to top

Introduction to the Angular Testing Utilities

Many tests explore how applications classes interact with Angular and the DOM while under Angular's control.

Such tests are easy to write with the help of the Angular testing utilities which include the TestBed class and some helper functions.

Tests written with these utilities are the main focus of this chapter. But they are not the only tests you should write.

Isolated unit tests

Isolated unit tests examine an instance of a class all by itself without any dependence on Angular or any injected values. The tester creates a test instance of the class with new, supplying test doubles for the constructor parameters as needed, and then probes the test instance API surface.

You can and should write isolated unit tests for pipes and services.

app/shared/title-case.pipe.spec.ts (excerpt)

describe('TitleCasePipe', () => { // This pipe is a pure, stateless function so no need for BeforeEach let pipe = new TitleCasePipe(); it('transforms "abc" to "Abc"', () => { expect(pipe.transform('abc')).toBe('Abc'); }); });

Components can be tested in isolation as well. However, isolated unit tests don't reveal how these classes interact with Angular. In particular, they can't reveal how a component class interacts with its own template or with other components. Such tests require the Angular testing utilities.

Testing with the Angular Testing Utilities

The Angular testing utilities include the TestBed class and several helper functions from @angular/core/testing.

The TestBed creates an Angular testing module — an @NgModule class — that you configure to produce the module environment for the class you want to test. You tell the TestBed to create an instance of the component-under-test and probe that instance with tests.

Before each spec, the TestBed resets itself to a base state. The base state includes a default testing module configuration consisting of the declarables (components, directives, and pipes) and providers (some of them mocked) that almost everyone needs.

The testing shims mentioned earlier initialize the testing module configuration to something like the BrowserModule from @angular/platform-browser.

This default configuration is merely a foundation for testing an app. You call TestBed.configureTestingModule with an object that defines additional imports, declarations, providers and schemas to fit your application tests. Optional override... methods can fine-tune aspects of the configuration.

After configuring the TestBed, tell it to create an instance of the component-under-test and the test fixture that you'll need to inspect and control the component's immediate environment.

app/banner.component.spec.ts (simplified)

beforeEach(() => { // refine the test module by declaring the test component TestBed.configureTestingModule({ declarations: [ BannerComponent ], }); // create component and test fixture fixture = TestBed.createComponent(BannerComponent); // get test component from the fixture comp = fixture.componentInstance; });

Angular tests can interact with the HTML in the test DOM, simulate user activity, tell Angular to perform specific task (such as change detection), and see the effects of these actions both in the component-under-test and in the test DOM.

app/banner.component.spec.ts (simplified)

it('should display original title', () => { // trigger change detection to update the view fixture.detectChanges(); // query for the title <h1> by CSS element selector de = fixture.debugElement.query(By.css('h1')); // confirm the element's content expect(de.nativeElement.textContent).toContain(comp.title); });

A comprehensive review of the Angular testing utilities appears later in the chapter. Let's dive right into Angular testing, starting with the components of a sample application.

Back to top

The sample application and its tests

This chapter tests a cut-down version of the Tour of Heroes tutorial app.

The following live example shows how it works and provides the complete source code.



The following live example runs all the tests of this application inside the browser, using the Jasmine Test Runner instead of karma.

It includes the tests discussed in this chapter and additional tests for you to explore. This live example contains both application and test code. Give it some time to load and warm up.

Back to top

Test a component

The top of the screen displays application title, presented by the BannerComponent in app/banner.component.ts.

app/banner.component.ts

import { Component } from '@angular/core'; @Component({ selector: 'app-banner', template: '<h1>{{title}}</h1>' }) export class BannerComponent { title = 'Test Tour of Heroes'; }

BannerComponent has an inline template and an interpolation binding. The component is probably too simple to be worth testing in real life but it's perfect for a first encounter with the TestBed.

The corresponding app/banner-component.spec.ts sits in the same folder as the component, for reasons explained here;

Start with ES6 import statements to get access to symbols referenced in the spec.

app/banner.component.spec.ts (imports)

import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; import { BannerComponent } from './banner.component';

Here's the setup for the tests followed by observations about the beforeEach:

app/banner.component.spec.ts (setup)

let comp: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let de: DebugElement; let el: HTMLElement; describe('BannerComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [ BannerComponent ], // declare the test component }); fixture = TestBed.createComponent(BannerComponent); comp = fixture.componentInstance; // BannerComponent test instance // query for the title <h1> by CSS element selector de = fixture.debugElement.query(By.css('h1')); el = de.nativeElement; }); });

TestBed.configureTestingModule takes an @NgModule-like metadata object. This one simply declares the component to test, BannerComponent.

It lacks imports because (a) it extends the default testing module configuration which already has what BannerComponent needs and (b) BannerComponent doesn't interact with any other components.

createComponent

TestBed.createComponent creates an instance of BannerComponent to test and returns a fixture.

TestBed.createComponent closes the current TestBed instance to further configuration. You cannot call any more TestBed configuration methods, not configureTestModule nor any of the override... methods. The TestBed throws an error if you try.

Do not configure the TestBed after calling createComponent.

ComponentFixture, DebugElement, and query(By.css)

The createComponent method returns a ComponentFixture, a handle on the test environment surrounding the created component. The fixture provides access to the component instance itself and to the DebugElement which is a handle on the component's DOM element.

The title property value was interpolated into the DOM within <h1> tags. Use the fixture's DebugElement to query for the <h1> element by CSS selector.

The query method takes a predicate function and searches the fixture's entire DOM tree for the first element that satisfies the predicate. The result is a different DebugElement, one associated with the matching DOM element.

The queryAll method returns an array of all DebugElements that satisfy the predicate.

A predicate is a function that returns a boolean. A query predicate receives a DebugElement and returns true if the element meets the selection criteria.

The By class is an Angular testing utility that produces useful predicates. Its By.css static method produces a standard CSS selector predicate that filters the same way as a jQuery selector.

Finally, the setup assigns the DOM element from the DebugElement nativeElement property to el. The tests will assert that el contains the expected title text.

The tests

Jasmine runs the beforeEach function before each of these tests

app/banner.component.spec.ts (tests)

it('should display original title', () => { fixture.detectChanges(); expect(el.textContent).toContain(comp.title); }); it('should display a different test title', () => { comp.title = 'Test Title'; fixture.detectChanges(); expect(el.textContent).toContain('Test Title'); });

These tests ask the DebugElement for the native HTML element to satisfy their expectations.

detectChanges: Angular change detection within a test

Each test tells Angular when to perform change detection by calling fixture.detectChanges(). The first test does so immediately, triggering data binding and propagation of the title property to the DOM element.

The second test changes the component's title property and only then calls fixture.detectChanges(); the new value appears in the DOM element.

In production, change detection kicks in automatically when Angular creates a component or the user enters a keystroke or an asynchronous activity (e.g., AJAX) completes.

The TestBed.createComponent does not trigger change detection. The fixture does not automatically push the component's title property value into the data bound element, a fact demonstrated in the following test:

app/banner.component.spec.ts (no detectChanges)

it('no title in the DOM until manually call `detectChanges`', () => { expect(el.textContent).toEqual(''); });

This behavior (or lack of it) is intentional. It gives the tester an opportunity to inspect or change the state of the component before Angular initiates data binding or calls lifecycle hooks.

Automatic change detection

Some testers prefer that the Angular test environment run change detection automatically. That's possible by configuring the TestBed with the AutoDetect provider:

app/banner.component.spec.ts (AutoDetect)

fixture = TestBed.configureTestingModule({ declarations: [ BannerComponent ], providers: [ { provide: ComponentFixtureAutoDetect, useValue: true } ] })

Here are three tests that illustrate how AutoDetect works.

app/banner.component.spec.ts (AutoDetect Tests)

it('should display original title', () => { // Hooray! No `fixture.detectChanges()` needed expect(el.textContent).toContain(comp.title); }); it('should still see original title after comp.title change', () => { const oldTitle = comp.title; comp.title = 'Test Title'; // Displayed title is old because Angular didn't hear the change :( expect(el.textContent).toContain(oldTitle); }); it('should display updated title after detectChanges', () => { comp.title = 'Test Title'; fixture.detectChanges(); // detect changes explicitly expect(el.textContent).toContain(comp.title); });

The first test shows the benefit of automatic change detection.

The second and third test reveal an important limitation. The Angular testing environment does not know that the test changed the component's title. AutoDetect responds to asynchronous activities such as promise resolution, timers, and DOM events. But a direct, synchronous update of the component property is invisible to AutoDetect. The test must call fixture.detectChanges() manually to trigger another cycle of change detection.

Rather than wonder when the test fixture will or won't perform change detection, the samples in this chapter always call detectChanges() explicitly. There is no harm in calling detectChanges() more often than is strictly necessary.

Back to top

Test a component with a dependency

Components often have service dependencies. The WelcomeComponent displays a welcome message to the logged in user. It knows who the user is based on a property of the injected UserService:

app/welcome.component.ts

import { Component, OnInit } from '@angular/core'; import { UserService } from './model'; @Component({ selector: 'app-welcome', template: '<h3 class="welcome" ><i>{{welcome}}</i></h3>' }) export class WelcomeComponent implements OnInit { welcome = '-- not initialized yet --'; constructor(private userService: UserService) { } ngOnInit(): void { this.welcome = this.userService.isLoggedIn ? 'Welcome, ' + this.userService.user.name : 'Please log in.'; } }

The WelcomeComponent has decision logic that interacts with the service, logic that makes this component worth testing. Here's the testing module configuration for the spec file, app/welcome.component.spec.ts:

app/welcome.component.spec.ts

TestBed.configureTestingModule({ declarations: [ WelcomeComponent ], // providers: [ UserService ] // NO! Don't provide the real service! // Provide a test-double instead providers: [ {provide: UserService, useValue: userServiceStub } ] });

This time, in addition to declaring the component-under-test, the configuration adds a UserService provider to the providers list. But not the real UserService.

Provide service test doubles

A component-under-test doesn't have to be injected with real services. In fact, it is usually better if they are test doubles (stubs, fakes, spies, or mocks). The purpose of the spec is to test the component, not the service, and real services can be trouble.

Injecting the real UserService could be a nightmare. The real service might try to ask the user for login credentials and try to reach an authentication server. These behaviors could be hard to intercept. It is far easier and safer to create and register a test double in place of the real UserService.

This particular test suite supplies a minimal UserService stub that satisfies the needs of the WelcomeComponent and its tests:

userServiceStub = { isLoggedIn: true, user: { name: 'Test User'} };

Get injected services

The tests need access to the (stub) UserService injected into the WelcomeComponent.

Angular has a hierarchical injection system. There can be injectors at multiple levels, from the root injector created by the TestBed down through the component tree.

The safest way to get the injected service, the way that always works, is to get it from the injector of the component-under-test. The component injector is a property of the fixture's DebugElement.

WelcomeComponent's injector

// UserService actually injected into the component userService = fixture.debugElement.injector.get(UserService);

TestBed.get

You may also be able to get the service from the root injector via TestBed.get. This is easier to remember and less verbose. But it only works when Angular injects the component with the service instance in the test's root injector. Fortunately, in this test suite, the only provider of UserService is the root testing module, so it is safe to call TestBed.get as follows:

TestBed injector

// UserService from the root injector userService = TestBed.get(UserService);

The inject utility function is another way to get one or more services from the test root injector.

See the section "Override Component Providers" for a use case in which inject and TestBed.get do not work and you must get the service from the component's injector.

Always get the service from an injector

Surprisingly, you dare not reference the userServiceStub object that was provided to the testing module in the body of your test. It does not work! The userService instance injected into the component is a completely different object, a clone of the provided userServiceStub.

it('stub object and injected UserService should not be the same', () => { expect(userServiceStub === userService).toBe(false); // Changing the stub object has no effect on the injected service userServiceStub.isLoggedIn = false; expect(userService.isLoggedIn).toBe(true); });

Final setup and tests

Here's the complete beforeEach using TestBed.get:

app/welcome.component.spec.ts

beforeEach(() => { // stub UserService for test purposes userServiceStub = { isLoggedIn: true, user: { name: 'Test User'} }; TestBed.configureTestingModule({ declarations: [ WelcomeComponent ], providers: [ {provide: UserService, useValue: userServiceStub } ] }); fixture = TestBed.createComponent(WelcomeComponent); comp = fixture.componentInstance; // UserService from the root injector userService = TestBed.get(UserService); // get the "welcome" element by CSS selector (e.g., by class name) de = fixture.debugElement.query(By.css('.welcome')); el = de.nativeElement; });

And here are some tests:

app/welcome.component.spec.ts

it('should welcome the user', () => { fixture.detectChanges(); const content = el.textContent; expect(content).toContain('Welcome', '"Welcome ..."'); expect(content).toContain('Test User', 'expected name'); }); it('should welcome "Bubba"', () => { userService.user.name = 'Bubba'; // welcome message hasn't been shown yet fixture.detectChanges(); expect(el.textContent).toContain('Bubba'); }); it('should request login if not logged in', () => { userService.isLoggedIn = false; // welcome message hasn't been shown yet fixture.detectChanges(); const content = el.textContent; expect(content).not.toContain('Welcome', 'not welcomed'); expect(content).toMatch(/log in/i, '"log in"'); });

The first is a sanity test; it confirms that the stubbed UserService is called and working.

The second parameter to the Jasmine it (e.g., 'expected name') is an optional addendum. If the expectation fails, Jasmine displays this addendum after the expectation failure message. It can help clarify what went wrong and which expectation failed in a spec with multiple expectations.

The remaining tests confirm the logic of the component when the service returns different values. The second test validates the effect of changing the user name. The third test checks that the component displays the proper message when there is no logged-in user.

Back to top

Test a component with an async service

Many services return values asynchronously. Most data services make an HTTP request to a remote server and the response is necessarily asynchronous.

The "About" view in this sample displays Mark Twain quotes. The TwainComponent handles the display, delegating the server request to the TwainService. Both are in the app/shared folder because the author intends to display Twain quotes on other pages someday. Here is the TwainComponent.

app/shared/twain.component.ts

@Component({ selector: 'twain-quote', template: '<p class="twain"><i>{{quote}}</i></p>' }) export class TwainComponent implements OnInit { intervalId: number; quote = '...'; constructor(private twainService: TwainService) { } ngOnInit(): void { this.twainService.getQuote().then(quote => this.quote = quote); } }

The TwainService implementation is irrelevant at this point. It is sufficient to see within ngOnInit that twainService.getQuote returns a promise which means it is asynchronous.

In general, tests should not make calls to remote servers. They should emulate such calls. The setup in this app/shared/twain.component.spec.ts shows one way to do that:

app/shared/twain.component.spec.ts (setup)

beforeEach(() => { TestBed.configureTestingModule({ declarations: [ TwainComponent ], providers: [ TwainService ], }); fixture = TestBed.createComponent(TwainComponent); comp = fixture.componentInstance; // TwainService actually injected into the component twainService = fixture.debugElement.injector.get(TwainService); // Setup spy on the `getQuote` method spy = spyOn(twainService, 'getQuote') .and.returnValue(Promise.resolve(testQuote)); // Get the Twain quote element by CSS selector (e.g., by class name) de = fixture.debugElement.query(By.css('.twain')); el = de.nativeElement; });

Spying on the real service

This setup is similar to the welcome.component.spec setup. But instead of creating a stubbed service object, it injects the real service (see the testing module providers) and replaces the critical getQuote method with a Jasmine spy.

spy = spyOn(twainService, 'getQuote') .and.returnValue(Promise.resolve(testQuote));

The spy is designed such that any call to getQuote receives an immediately resolved promise with a test quote. The spy bypasses the actual getQuote method and therefore will not contact the server.

Faking a service instance and spying on the real service are both great options. Pick the one that seems easiest for the current test suite. Don't be afraid to change your mind.

Here are the tests with commentary to follow:

app/shared/twain.component.spec.ts (tests)

it('should not show quote before OnInit', () => { expect(el.textContent).toBe('', 'nothing displayed'); expect(spy.calls.any()).toBe(false, 'getQuote not yet called'); }); it('should still not show quote after component initialized', () => { fixture.detectChanges(); // getQuote service is async => still has not returned with quote expect(el.textContent).toBe('...', 'no quote yet'); expect(spy.calls.any()).toBe(true, 'getQuote called'); }); it('should show quote after getQuote promise (async)', async(() => { fixture.detectChanges(); fixture.whenStable().then(() => { // wait for async getQuote fixture.detectChanges(); // update view with quote expect(el.textContent).toBe(testQuote); }); })); it('should show quote after getQuote promise (fakeAsync)', fakeAsync(() => { fixture.detectChanges(); tick(); // wait for async getQuote fixture.detectChanges(); // update view with quote expect(el.textContent).toBe(testQuote); }));

Synchronous tests

The first two tests are synchronous. Thanks to the spy, they verify that getQuote is called after the first change detection cycle during which Angular calls ngOnInit.

Neither test can prove that a value from the service is be displayed. The quote itself has not arrived, despite the fact that the spy returns a resolved promise.

This test must wait at least one full turn of the JavaScript engine before the value becomes available. The test must become asynchronous.

The async function in it

Notice the async in the third test.

app/shared/twain.component.spec.ts (async test)

it('should show quote after getQuote promise (async)', async(() => { fixture.detectChanges(); fixture.whenStable().then(() => { // wait for async getQuote fixture.detectChanges(); // update view with quote expect(el.textContent).toBe(testQuote); }); }));

The async function is one of the Angular testing utilities. It simplifies coding of asynchronous tests by arranging for the tester's code to run in a special async test zone.

The async function takes a parameterless function and returns a function which becomes the argument to the Jasmine it call.

The body of the async argument looks much like the body of a normal it argument. There is nothing obviously asynchronous about it. For example, it doesn't return a promise and there is no done function to call as there is in standard Jasmine asynchronous tests.

Some functions called within a test (such as fixture.whenStable) continue to reveal their asynchronous behavior.

The fakeAsync alternative, covered below, removes this artifact and affords a more linear coding experience.

whenStable

The test must wait for the getQuote promise to resolve in the next turn of the JavaScript engine.

This test has no direct access to the promise returned by the call to testService.getQuote which is private and inaccessible inside TwainComponent.

Fortunately, the getQuote promise is accessible to the async test zone which intercepts all promises issued within the async method call.

The ComponentFixture.whenStable method returns its own promise which resolves when the getQuote promise completes. In fact, the whenStable promise resolves when all pending asynchronous activities within this test complete ... the definition of "stable".

Then the test resumes and kicks off another round of change detection (fixture.detectChanges) which tells Angular to update the DOM with the quote. The getQuote helper method extracts the display element text and the expectation confirms that the text matches the test quote.

The fakeAsync function

The fourth test verifies the same component behavior in a different way.

app/shared/twain.component.spec.ts (fakeAsync test)

it('should show quote after getQuote promise (fakeAsync)', fakeAsync(() => { fixture.detectChanges(); tick(); // wait for async getQuote fixture.detectChanges(); // update view with quote expect(el.textContent).toBe(testQuote); }));

Notice that fakeAsync replaces async as the it argument. The fakeAsync function is another of the Angular testing utilities.

Like async, it takes a parameterless function and returns a function that becomes the argument to the Jasmine it call.

The fakeAsync function enables a linear coding style by running the test body in a special fakeAsync test zone.

The principle advantage of fakeAsync over async is that the test appears to be synchronous. There is no then(...) to disrupt the visible flow of control. The promise-returning fixture.whenStable is gone, replaced by tick().

There are limitations. For example, you cannot make an XHR call from within a fakeAsync.

The tick function

The tick function is one of the Angular testing utilities and a companion to fakeAsync. It can only be called within a fakeAsync body.

Calling tick() simulates the passage of time until all pending asynchronous activities complete, including the resolution of the getQuote promise in this test case.

It returns nothing. There is no promise to wait for. Proceed with the same test code as formerly appeared within the whenStable.then() callback.

Even this simple example is easier to read than the third test. To more fully appreciate the improvement, imagine a succession of asynchronous operations, chained in a long sequence of promise callbacks.

jasmine.done

While fakeAsync and even async function greatly simplify Angular asynchronous testing, you can still fallback to the traditional Jasmine asynchronous testing technique.

You can still pass it a function that takes a done callback. Now you are responsible for chaining promises, handling errors, and calling done at the appropriate moment.

Here is a done version of the previous two tests:

app/shared/twain.component.spec.ts (done test)

it('should show quote after getQuote promise (done)', done => { fixture.detectChanges(); // get the spy promise and wait for it to resolve spy.calls.mostRecent().returnValue.then(() => { fixture.detectChanges(); // update view with quote expect(el.textContent).toBe(testQuote); done(); }); });

Although we have no direct access to the getQuote promise inside TwainComponent, the spy does and that makes it possible to wait for getQuote to finish.

The jasmine.done technique, while discouraged, may become necessary when neither async nor fakeAsync can tolerate a particular asynchronous activity. That's rare but it happens.

Back to top

Test a component with an external template

The TestBed.createComponent is a synchronous method. It assumes that everything it could need is already in memory.

That has been true so far. Each tested component's @Component metadata has a template property specifying an inline templates. Neither component had a styleUrls property. Everything necessary to compile them was in memory at test runtime.

The DashboardHeroComponent is different. It has an external template and external css file, specified in templateUrl and styleUrls properties.

app/dashboard/dashboard-hero.component.ts (component)

@Component({ moduleId: module.id, selector: 'dashboard-hero', templateUrl: 'dashboard-hero.component.html', styleUrls: [ 'dashboard-hero.component.css' ] }) export class DashboardHeroComponent { @Input() hero: Hero; @Output() selected = new EventEmitter<Hero>(); click() { this.selected.next(this.hero); } }

The compiler must read these files from a file system before it can create a component instance.

The TestBed.compileComponents method asynchronously compiles all the components configured in its current testing module. After it completes, external templates and css files, have been "inlined" and TestBed.createComponent can do its job synchronously.

WebPack developers need not call compileComponents because it inlines templates and css as part of the automated build process that precedes running the test.

The app/dashboard/dashboard-hero.component.spec.ts demonstrates the pre-compilation process:

app/dashboard/dashboard-hero.component.spec.ts (compileComponents)

// async beforeEach beforeEach( async(() => { TestBed.configureTestingModule({ declarations: [ DashboardHeroComponent ], }) .compileComponents(); // compile template and css }));

The async function in beforeEach

Notice the async call in the beforeEach, made necessary by the asynchronous TestBed.compileComponents method. The async function arranges for the tester's code to run in a special async test zone that hides the mechanics of asynchronous execution, just as it does when passed to an it test.

compileComponents

In this example, TestBed.compileComponents compiles one component, the DashboardComponent. It's the only declared component in this testing module.

Tests later in this chapter have more declared components and some of them import application modules that declare yet more components. Some or all of these components could have external templates and css files. TestBed.compileComponents compiles them all asynchronously at one time.

The compileComponents method returns a promise so you can perform additional tasks after it finishes. The promise isn't needed here.

compileComponents closes configuration

Calling compileComponents closes the current TestBed instance is further configuration. You cannot call any more TestBed configuration methods, not configureTestModule nor any of the override... methods. The TestBed throws an error if you try.

Do not configure the TestBed after calling compileComponents. Make compileComponents the last step before calling TestBed.createComponent to instantiate the component-under-test.

The DashboardHeroComponent spec follows the asynchonous beforeEach with a synchronous beforeEach that completes the setup steps and runs tests ... as described in the next section.

Test a component with inputs and outputs

A component with inputs and outputs typically appears inside the view template of a host component. The host uses a property binding to set the input property and uses an event binding to listen to events raised by the output property.

The testing goal is to verify that such bindings work as expected. The tests should set input values and listen for output events.

The DashboardHeroComponent is a tiny example of a component in this role. It displays an individual hero provided by the DashboardComponent. Clicking that hero tells the DashboardComponent that the user has selected the hero.

The DashboardHeroComponent is embedded in the DashboardComponent template like this:

app/dashboard/dashboard.component.html (excerpt)

<dashboard-hero *ngFor="let hero of heroes" class="col-1-4" [hero]=hero (selected)="gotoDetail($event)" > </dashboard-hero>

The DashboardHeroComponent appears in an *ngFor repeater which sets each component's hero input property to the iteration value and listens for the components selected event.

Here's the component's definition again:

app/dashboard/dashboard-hero.component.ts (component)

@Component({ moduleId: module.id, selector: 'dashboard-hero', templateUrl: 'dashboard-hero.component.html', styleUrls: [ 'dashboard-hero.component.css' ] }) export class DashboardHeroComponent { @Input() hero: Hero; @Output() selected = new EventEmitter<Hero>(); click() { this.selected.next(this.hero); } }

While testing a component this simple has little intrinsic value, it's worth knowing how. Three approaches come to mind:

  1. Test it as used by DashboardComponent
  2. Test it as a stand-alone component
  3. Test it as used by a substitute for DashboardComponent

A quick look at the DashboardComponent constructor discourages the first approach:

app/dashboard/dashboard.component.ts (constructor)

constructor( private router: Router, private heroService: HeroService) { }

The DashboardComponent depends upon the Angular router and the HeroService. You'd probably have to replace them both with test doubles and that looks like a lot of work. The router seems particularly challenging.

The discussion below covers testing components that require the router.

The immediate goal is to test the DashboardHeroComponent, not the DashboardComponent, and there's no need to work hard unnecessarily. Let's try the second and third options.

Test DashboardHeroComponent stand-alone

Here's the spec file setup.

app/dashboard/dashboard-hero.component.spec.ts (setup)

// async beforeEach beforeEach( async(() => { TestBed.configureTestingModule({ declarations: [ DashboardHeroComponent ], }) .compileComponents(); // compile template and css })); // synchronous beforeEach beforeEach(() => { fixture = TestBed.createComponent(DashboardHeroComponent); comp = fixture.componentInstance; heroEl = fixture.debugElement.query(By.css('.hero')); // find hero element // pretend that it was wired to something that supplied a hero expectedHero = new Hero(42, 'Test Name'); comp.hero = expectedHero; fixture.detectChanges(); // trigger initial data binding });

The async beforeEach was discussed above. Having compiled the components asynchronously with compileComponents, the rest of the setup proceeds synchronously in a second beforeEach, using the basic techniques described earlier.

Note how the setup code assigns a test hero (expectedHero) to the component's hero property, emulating the way the DashboardComponent would set it via the property binding in its repeater.

The first test follows:

app/dashboard/dashboard-hero.component.spec.ts (name test)

it('should display hero name', () => { const expectedPipedName = expectedHero.name.toUpperCase(); expect(heroEl.nativeElement.textContent).toContain(expectedPipedName); });

It verifies that the hero name is propagated through to template with a binding. There's a twist. The template passes the hero name through the Angular UpperCasePipe so the test must match the element value with the uppercased name:

<div (click)="click()" class="hero"> {{hero.name | uppercase}} </div>

This small test demonstrates how Angular tests can verify a component's visual representation — something not possible with isolated unit tests — at low cost and without resorting to much slower and more complicated end-to-end tests.

The second test verifies click behavior. Clicking the hero should raise a selected event that the host component (DashboardComponent presumably) can hear:

app/dashboard/dashboard-hero.component.spec.ts (click test)

it('should raise selected event when clicked', () => { let selectedHero: Hero; comp.selected.subscribe((hero: Hero) => selectedHero = hero); heroEl.triggerEventHandler('click', null); expect(selectedHero).toBe(expectedHero); });

The component exposes an EventEmitter property. The test subscribes to it just as the host component would do.

The heroEl is a DebugElement that represents the hero <div>. The test calls triggerEventHandler with the "click" event name. The "click" event binding responds by calling DashboardHeroComponent.click().

If the component behaves as expected, click() tells the component's selected property to emit the hero object, the test detects that value through its subscription to selected, and the test should pass.

triggerEventHandler

The Angular DebugElement.triggerEventHandler can raise any data-bound event by its event name. The second parameter is the event object passed to the handler.

In this example, the test triggers a "click" event with a null event object.

heroEl.triggerEventHandler('click', null);

The test assumes (correctly in this case) that the runtime event handler — the component's click() method — doesn't care about the event object.

Other handlers will be less forgiving. For example, the RouterLink directive expects an object with a button property indicating the mouse button that was pressed. The directive throws an error if the event object doesn't do this correctly.

Clicking a button, an anchor, or an arbitrary HTML element is a common test task. Make that easy by encapsulating the click-triggering process in a helper such as the click function below:

testing/index.ts (click helper)

/** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */ export const ButtonClickEvents = { left: { button: 0 }, right: { button: 2 } }; /** Simulate element click. Defaults to mouse left-button click event. */ export function click(el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void { if (el instanceof HTMLElement) { el.click(); } else { el.triggerEventHandler('click', eventObj); } }

The first parameter is the element-to-click. You can pass a custom event object as the second parameter if you wish. The default is a (partial) left-button mouse event object accepted by many handlers including the RouterLink directive.

click() is not an Angular testing utility

The click() helper function is not one of the Angular testing utilities. It's a function defined in this chapter's sample code and used by all of the sample tests. If you like it, add it to your own collection of helpers.

Here's the previous test, rewritten using this click helper.

app/dashboard/dashboard-hero.component.spec.ts (click test revised)

it('should raise selected event when clicked', () => { let selectedHero: Hero; comp.selected.subscribe((hero: Hero) => selectedHero = hero); click(heroEl); // triggerEventHandler helper expect(selectedHero).toBe(expectedHero); });

Test a component inside a test host component

In the previous approach the tests themselves played the role of the host DashboardComponent. A nagging suspicion remains. Will the DashboardHeroComponent work properly when properly data-bound to a host component?

Testing with the actual DashboardComponent host is doable but seems more trouble than its worth. It's easier to emulate the DashboardComponent host with a test host like this one:

app/dashboard/dashboard-hero.component.spec.ts (test host)

@Component({ template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"></dashboard-hero>` }) class TestHostComponent { hero = new Hero(42, 'Test Name'); selectedHero: Hero; onSelected(hero: Hero) { this.selectedHero = hero; } }

The test host binds to DashboardHeroComponent as the DashboardComponent would but without the distraction of the Router, the HeroService or even the *ngFor repeater.

The test host sets the component's hero input property with its test hero. It binds the component's selected event with its onSelected handler that records the emitted hero in its selectedHero property. Later the tests check that property to verify that the DashboardHeroComponent.selected event really did emit the right hero.

The setup for the test-host tests is similar to the setup for the stand-alone tests:

app/dashboard/dashboard-hero.component.spec.ts (test host setup)

beforeEach( async(() => { TestBed.configureTestingModule({ declarations: [ DashboardHeroComponent, TestHostComponent ], // declare both }).compileComponents(); })); beforeEach(() => { // create TestHostComponent instead of DashboardHeroComponent fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; heroEl = fixture.debugElement.query(By.css('.hero')); // find hero fixture.detectChanges(); // trigger initial data binding });

This testing module configuration shows two important differences:

  1. It declares both the DashboardHeroComponent and the TestHostComponent.
  2. It creates the TestHostComponent instead of the DashboardHeroComponent.

The fixture returned by createComponent holds an instance of TestHostComponent instead of an instance of DashboardHeroComponent.

Of course creating the TestHostComponent has the side-effect of creating a DashboardHeroComponent because the latter appears within the template of the former. The query for the hero element (heroEl) still finds it in the test DOM albeit at greater depth in the element tree than before.

The tests themselves are almost identical to the stand-alone version

app/dashboard/dashboard-hero.component.spec.ts (test-host)

it('should display hero name', () => { const expectedPipedName = testHost.hero.name.toUpperCase(); expect(heroEl.nativeElement.textContent).toContain(expectedPipedName); }); it('should raise selected event when clicked', () => { click(heroEl); // selected hero should be the same data bound hero expect(testHost.selectedHero).toBe(testHost.hero); });

Only the selected event test differs. It confirms that the selected DashboardHeroComponent hero really does find its way up through the event binding to the host component.

Back to top

Test a routed component

Testing the actual DashboardComponent seemed daunting because it injects the Router.

app/dashboard/dashboard.component.ts (constructor)

constructor( private router: Router, private heroService: HeroService) { }

It also injects the HeroService but faking that is a familiar story. The Router has a complicated API and is entwined with other services and application pre-conditions.

Fortunately, the DashboardComponent isn't doing much with the Router

app/dashboard/dashboard.component.ts (goToDetail)

gotoDetail(hero: Hero) { let url = `/heroes/${hero.id}`; this.router.navigateByUrl(url); }

This is often the case. As a rule you test the component, not the router, and care only if the component navigates with the right address under the given conditions. Stubbing the router with a test implementation is an easy option. This should do the trick:

app/dashboard/dashboard.component.spec.ts (Router Stub)

class RouterStub { navigateByUrl(url: string) { return url; } }

Now we setup the testing module with the test stubs for the Router and HeroService and create a test instance of the DashboardComponent for subsequent testing.

app/dashboard/dashboard.component.spec.ts (compile and create)

beforeEach( async(() => { TestBed.configureTestingModule({ providers: [ { provide: HeroService, useClass: FakeHeroService }, { provide: Router, useClass: RouterStub } ] }) .compileComponents().then(() => { fixture = TestBed.createComponent(DashboardComponent); comp = fixture.componentInstance; });

The following test clicks the displayed hero and confirms (with the help of a spy) that Router.navigateByUrl is called with the expected url.

app/dashboard/dashboard.component.spec.ts (navigate test)

it('should tell ROUTER to navigate when hero clicked', inject([Router], (router: Router) => { // ... const spy = spyOn(router, 'navigateByUrl'); heroClick(); // trigger click on first inner <div class="hero"> // args passed to router.navigateByUrl() const navArgs = spy.calls.first().args[0]; // expecting to navigate to id of the component's first hero const id = comp.heroes[0].id; expect(navArgs).toBe('/heroes/' + id, 'should nav to HeroDetail for first hero'); }));

The inject function

Notice the inject function in the second it argument.

it('should tell ROUTER to navigate when hero clicked', inject([Router], (router: Router) => { // ... }));

The inject function is one of the Angular testing utilities. It injects services into the test function where you can alter, spy on, and manipulate them.

The inject function has two parameters

  1. an array of Angular dependency injection tokens
  2. a test function whose parameters correspond exactly to each item in the injection token array
inject uses the TestBed Injector

The inject function uses the current TestBed injector and can only return services provided at that level. It does not return services from component providers.

This example injects the Router from the current TestBed injector. That's fine for this test because the Router is (and must be) provided by the application root injector.

If you need a service provided by the component's own injector, call fixture.debugElement.injector.get instead:

Component's injector

// UserService actually injected into the component userService = fixture.debugElement.injector.get(UserService);

Use the component's own injector to get the service actually injected into the component.

The inject function closes the current TestBed instance to further configuration. You cannot call any more TestBed configuration methods, not configureTestModule nor any of the override... methods. The TestBed throws an error if you try.

Do not configure the TestBed after calling inject.

Back to top

Test a routed component with parameters

Clicking a Dashboard hero triggers navigation to heroes/:id where :id is a route parameter whose value is the id of the hero to edit. That URL matches a route to the HeroDetailComponent.

The router pushes the :id token value into the ActivatedRoute.params Observable property, Angular injects the ActivatedRoute into the HeroDetailComponent, and the component extracts the id so it can fetch the corresponding hero via the HeroDetailService. Here's the HeroDetailComponent constructor:

app/hero/hero-detail.component.ts (constructor)

constructor( private heroDetailService: HeroDetailService, private route: ActivatedRoute, private router: Router) { }

HeroDetailComponent listens for changes to the ActivatedRoute.params in its ngOnInit method.

app/hero/hero-detail.component.ts (ngOnInit)

ngOnInit(): void { // get hero when `id` param changes this.route.params.pluck<string>('id') .forEach(id => this.getHero(id)) .catch(() => this.hero = new Hero()); // no id; should edit new hero }

The expression after route.params chains an Observable operator that plucks the id from the params and then chains a forEach operator to subscribes to id-changing events. The id changes every time the user navigates to a different hero.

The forEach passes the new id value to the component's getHero method (not shown) which fetches a hero and sets the component's hero property. If theid parameter is missing, the pluck operator fails and the catch treats failure as a request to edit a new hero.

The Router chapter covers ActivatedRoute.params in more detail.

A test can explore how the HeroDetailComponent responds to different id parameter values by manipulating the ActivatedRoute injected into the component's constructor.

By now you know how to stub the Router and a data service. Stubbing the ActivatedRoute would follow the same pattern except for a complication: the ActivatedRoute.params is an Observable.

Observable test double

The hero-detail.component.spec.ts relies on an ActivatedRouteStub to set ActivatedRoute.params values for each test. This is a cross-application, re-usable test helper class. We recommend locating such helpers in a testing folder sibling to the app folder. This sample keeps ActivatedRouteStub in testing/router-stubs.ts:

testing/router-stubs.ts (ActivatedRouteStub)

import { BehaviorSubject } from 'rxjs/BehaviorSubject'; @Injectable() export class ActivatedRouteStub { // ActivatedRoute.params is Observable private subject = new BehaviorSubject(this.testParams); params = this.subject.asObservable(); // Test parameters private _testParams: {}; get testParams() { return this._testParams; } set testParams(params: {}) { this._testParams = params; this.subject.next(params); } // ActivatedRoute.snapshot.params get snapshot() { return { params: this.testParams }; } }

Notable features of this stub:

The snapshot is another popular way for components to consume route parameters.

The router stubs in this chapter are meant to inspire you. Create your own stubs to fit your testing needs.

Observable tests

Here's a test demonstrating the component's behavior when the observed id refers to an existing hero:

app/hero/hero-detail.component.spec.ts (existing id)

describe('when navigate to existing hero', () => { let expectedHero: Hero; beforeEach( async(() => { expectedHero = firstHero; activatedRoute.testParams = { id: expectedHero.id }; createComponent(); })); it('should display that hero\'s name', () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); });

The createComponent method and page object are discussed in the next section. Rely on your intuition for now.

When the id cannot be found, the component should re-route to the HeroListComponent. The test suite setup provided the same RouterStub described above which spies on the router without actually navigating. This test supplies a "bad" id and expects the component to try to navigate.

app/hero/hero-detail.component.spec.ts (bad id)

describe('when navigate to non-existant hero id', () => { beforeEach( async(() => { activatedRoute.testParams = { id: 99999 }; createComponent(); })); it('should try to navigate back to hero list', () => { expect(page.gotoSpy.calls.any()).toBe(true, 'comp.gotoList called'); expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called'); }); });

While this app doesn't have a route to the HeroDetailComponent that omits the id parameter, it might add such a route someday. The component should do something reasonable when there is no id.

In this implementation, the component should create and display a new hero. New heroes have id=0 and a blank name. This test confirms that the component behaves as expected:

app/hero/hero-detail.component.spec.ts (no id)

describe('when navigate with no hero id', () => { beforeEach( async( createComponent )); it('should have hero.id === 0', () => { expect(comp.hero.id).toBe(0); }); it('should display empty hero name', () => { expect(page.nameDisplay.textContent).toBe(''); }); });

Inspect and download all of the chapter's application test code with this live example.

Use a page object to simplify setup

The HeroDetailComponent is a simple view with a title, two hero fields, and two buttons.

HeroDetailComponent in action

But there's already plenty of template complexity.

app/hero/hero-detail.component.html

<div *ngIf="hero"> <h2><span>{{hero.name | titlecase}}</span> Details</h2> <div> <label>id: </label>{{hero.id}}</div> <div> <label for="name">name: </label> <input id="name" [(ngModel)]="hero.name" placeholder="name" /> </div> <button (click)="save()">Save</button> <button (click)="cancel()">Cancel</button> </div>

To fully exercise the component, the test needs ...

Even a small form such as this one can produce a mess of tortured conditional setup and CSS element selection.

Tame the madness with a Page class that simplifies access to component properties and encapsulates the logic that sets them. Here's the Page class for the hero-detail.component.spec.ts

app/hero/hero-detail.component.spec.ts (Page)

class Page { gotoSpy: jasmine.Spy; navSpy: jasmine.Spy; saveSpy: jasmine.Spy; saveBtn: DebugElement; cancelBtn: DebugElement; nameDisplay: HTMLElement; nameInput: HTMLInputElement; constructor() { // Use component's injector to see the services it injected. const compInjector = fixture.debugElement.injector; const hds = compInjector.get(HeroDetailService); const router = compInjector.get(Router); this.gotoSpy = spyOn(comp, 'gotoList').and.callThrough(); this.navSpy = spyOn(router, 'navigate'); this.saveSpy = spyOn(hds, 'saveHero').and.callThrough(); } /** Add page elements after hero arrives */ addPageElements() { if (comp.hero) { // have a hero so these elements are now in the DOM const buttons = fixture.debugElement.queryAll(By.css('button')); this.saveBtn = buttons[0]; this.cancelBtn = buttons[1]; this.nameDisplay = fixture.debugElement.query(By.css('span')).nativeElement; this.nameInput = fixture.debugElement.query(By.css('input')).nativeElement; } } }

Now the important hooks for component manipulation and inspection are neatly organized and accessible from an instance of Page.

A createComponent method creates a page and fills in the blanks once the hero arrives.

app/hero/hero-detail.component.spec.ts (createComponent)

/** Create the HeroDetailComponent, initialize it, set test variables */ function createComponent() { fixture = TestBed.createComponent(HeroDetailComponent); comp = fixture.componentInstance; page = new Page(); // 1st change detection triggers ngOnInit which gets a hero fixture.detectChanges(); return fixture.whenStable().then(() => { // 2nd change detection displays the async-fetched hero fixture.detectChanges(); page.addPageElements(); }); }

The observable tests in the previous section demonstrate how createComponent and page keep the tests short and on message. There are no distractions: no waiting for promises to resolve and no searching the DOM for element values to compare. Here are a few more HeroDetailComponent tests to drive the point home.

app/hero/hero-detail.component.spec.ts (selected tests)

it('should display that hero\'s name', () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); it('should navigate when click cancel', () => { click(page.cancelBtn); expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called'); }); it('should save when click save but not navigate immediately', () => { click(page.saveBtn); expect(page.saveSpy.calls.any()).toBe(true, 'HeroDetailService.save called'); expect(page.navSpy.calls.any()).toBe(false, 'router.navigate not called'); }); it('should navigate when click save and save resolves', fakeAsync(() => { click(page.saveBtn); tick(); // wait for async save to complete expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called'); })); it('should convert hero name to Title Case', fakeAsync(() => { const inputName = 'quick BROWN fox'; const titleCaseName = 'Quick Brown Fox'; // simulate user entering new name into the input box page.nameInput.value = inputName; // dispatch a DOM event so that Angular learns of input value change. page.nameInput.dispatchEvent(newEvent('input')); // Tell Angular to update the output span through the title pipe fixture.detectChanges(); expect(page.nameDisplay.textContent).toBe(titleCaseName); }));
Back to top

Setup with module imports

Earlier component tests configured the testing module with a few declarations like this:

app/dashboard/dashboard-hero.component.spec.ts (config)

// async beforeEach beforeEach( async(() => { TestBed.configureTestingModule({ declarations: [ DashboardHeroComponent ], }) .compileComponents(); // compile template and css }));

The DashboardComponent is simple. It needs no help. But more complex components often depend on other components, directives, pipes, and providers and these must be added to the testing module too.

Fortunately, the TestBed.configureTestingModule parameter parallels the metadata passed to the @NgModule decorator which means you can also specify providers and imports.

The HeroDetailComponent requires a lot of help despite its small size and simple construction. In addition to the support it receives from the default testing module CommonModule, it needs:

One approach is to configure the testing module from the individual pieces as in this example:

app/hero/hero-detail.component.spec.ts (FormsModule setup)

beforeEach( async(() => { TestBed.configureTestingModule({ imports: [ FormsModule ], declarations: [ HeroDetailComponent, TitleCasePipe ], providers: [ { provide: ActivatedRoute, useValue: activatedRoute }, { provide: HeroService, useClass: FakeHeroService }, { provide: Router, useClass: RouterStub}, ] }) .compileComponents(); }));

Because many app components need the FormsModule and the TitleCasePipe, the developer created a SharedModule to combine these and other frequently requested parts. The test configuration can use the SharedModule too as seen in this alternative setup:

app/hero/hero-detail.component.spec.ts (SharedModule setup)

beforeEach( async(() => { TestBed.configureTestingModule({ imports: [ SharedModule ], declarations: [ HeroDetailComponent ], providers: [ { provide: ActivatedRoute, useValue: activatedRoute }, { provide: HeroService, useClass: FakeHeroService }, { provide: Router, useClass: RouterStub}, ] }) .compileComponents(); }));

It's a bit tighter and smaller, with fewer import statements (not shown).

Import the feature module

The HeroDetailComponent is part of the HeroModule Feature Module that aggregates more of the interdependent pieces including the SharedModule. Try a test configuration that imports the HeroModule like this one:

app/hero/hero-detail.component.spec.ts (HeroModule setup)

beforeEach( async(() => { TestBed.configureTestingModule({ imports: [ HeroModule ], providers: [ { provide: ActivatedRoute, useValue: activatedRoute }, { provide: HeroService, useClass: FakeHeroService }, { provide: Router, useClass: RouterStub}, ] }) .compileComponents(); }));

That's really crisp. Only the test doubles in the providers remain. Even the HeroDetailComponent declaration is gone.

In fact, if you try to declare it, Angular throws an error because HeroDetailComponent is declared in both the HeroModule and the DynamicTestModule (the testing module).

Importing the component's feature module is often the easiest way to configure the tests, especially when the feature module is small and mostly self-contained ... as feature modules should be.

Back to top

Override component providers

The HeroDetailComponent provides its own HeroDetailService.

app/hero/hero-detail.component.ts (prototype)

@Component({ moduleId: module.id, selector: 'app-hero-detail', templateUrl: 'hero-detail.component.html', styleUrls: ['hero-detail.component.css' ], providers: [ HeroDetailService ] }) export class HeroDetailComponent implements OnInit { constructor( private heroDetailService: HeroDetailService, private route: ActivatedRoute, private router: Router) { } }

It's not possible to stub the component's HeroDetailService in the providers of the TestBed.configureTestingModule. Those are providers for the testing module, not the component. They prepare the dependency injector at the fixture level.

Angular creates the component with its own injector which is a child of the fixture injector. It registers the component's providers (the HeroDetailService in this case) with the child injector. A test cannot get to child injector services from the fixture injector. And TestBed.configureTestingModule can't configure them either.

Angular has been creating new instances of the real HeroDetailService all along!

These tests could fail or timeout if the HeroDetailService made its own XHR calls to a remote server. There might not be a remote server to call.

Fortunately, the HeroDetailService delegates responsibility for remote data access to an injected HeroService.

app/hero/hero-detail.service.ts (prototype)

@Injectable() export class HeroDetailService { constructor(private heroService: HeroService) { } /* . . . */ }

The previous test configuration replaces the real HeroService with a FakeHeroService that intercepts server requests and fakes their responses.

What if you aren't so lucky. What if faking the HeroService is hard? What if HeroDetailService makes its own server requests?

The TestBed.overrideComponent method can replace the component's providers with easy-to-manage test doubles as seen in the following setup variation:

app/hero/hero-detail.component.spec.ts (Override setup)

beforeEach( async(() => { TestBed.configureTestingModule({ imports: [ HeroModule ], providers: [ { provide: ActivatedRoute, useValue: activatedRoute }, { provide: Router, useClass: RouterStub}, ] }) // Override component's own provider .overrideComponent(HeroDetailComponent, { set: { providers: [ { provide: HeroDetailService, useClass: StubHeroDetailService } ] } }) .compileComponents(); }));

Notice that TestBed.configureTestingModule no longer provides a (fake) HeroService because it's not needed.

The overrideComponent method

Focus on the overrideComponent method.

app/hero/hero-detail.component.spec.ts (overrideComponent)

.overrideComponent(HeroDetailComponent, { set: { providers: [ { provide: HeroDetailService, useClass: StubHeroDetailService } ] } })

It takes two arguments: the component type to override (HeroDetailComponent) and an override metadata object. The overide metadata object is a generic defined as follows:

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

A metadata override object can either add-and-remove elements in metadata properties or completely reset those properties. This example resets the component's providers metadata.

The type parameter, T, is the kind of metadata you'd pass to the @Component decorator:

selector?: string; template?: string; templateUrl?: string; providers?: any[]; ...

StubHeroDetailService

This example completely replaces the component's providers with an array containing the StubHeroDetailService. The StubHeroDetailService is dead simple. It doesn't need a HeroService (fake or otherwise).

app/hero/hero-detail.component.spec.ts (StubHeroDetailService)

class StubHeroDetailService { testHero = new Hero(42, 'Test Hero'); getHero(id: number | string): Promise<Hero> { return Promise.resolve(true).then(() => Object.assign({}, this.testHero) ); } saveHero(hero: Hero): Promise<Hero> { return Promise.resolve(true).then(() => Object.assign(this.testHero, hero) ); } }

The override tests

Now the tests can control the component's hero directly by manipulating the stub's testHero.

app/hero/hero-detail.component.spec.ts (override tests)

let hds: StubHeroDetailService; beforeEach( async(() => { createComponent(); // get the component's injected StubHeroDetailService hds = fixture.debugElement.injector.get(HeroDetailService); })); it('should display stub hero\'s name', () => { expect(page.nameDisplay.textContent).toBe(hds.testHero.name); }); it('should save stub hero change', fakeAsync(() => { const origName = hds.testHero.name; const newName = 'New Name'; page.nameInput.value = newName; page.nameInput.dispatchEvent(newEvent('input')); // tell Angular expect(comp.hero.name).toBe(newName, 'component hero has new name'); expect(hds.testHero.name).toBe(origName, 'service hero unchanged before save'); click(page.saveBtn); tick(); // wait for async save to complete expect(hds.testHero.name).toBe(newName, 'service hero has new name after save'); expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called'); }));

More overrides

The TestBed.overrideComponent method can be called multiple times for the same or different components. The TestBed offers similar overrideDirective, overrideModule, and overridePipe methods for digging into and replacing parts of these other classes.

Explore the options and combinations on your own.

Back to top

Test a RouterOutlet component

The AppComponent displays routed components in a <router-outlet>. It also displays a navigation bar with anchors and their RouterLink directives.

app/app.component.html

<app-banner></app-banner> <app-welcome></app-welcome> <nav> <a routerLink="/dashboard">Dashboard</a> <a routerLink="/heroes">Heroes</a> <a routerLink="/about">About</a> </nav> <router-outlet></router-outlet>

The component class does nothing.

app/app.component.ts

import { Component } from '@angular/core'; @Component({ moduleId: module.id, selector: 'my-app', templateUrl: 'app.component.html' }) export class AppComponent { }

Unit tests can confirm that the anchors are wired properly without engaging the router. See why this is worth doing below.

Stubbing unneeded components

The test setup should look familiar

app/app.component.spec.ts (Stub Setup)

beforeEach( async(() => { TestBed.configureTestingModule({ declarations: [ AppComponent, BannerComponent, WelcomeStubComponent, RouterLinkStubDirective, RouterOutletStubComponent ] }) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); }));

The AppComponent is the declared test subject The setup extends the default testing module with one real component (BannerComponent) and several stubs.

The component stubs are essential. Without them, the Angular compiler doesn't recognize the <app-welcome> and <router-outlet> tags and throws an error.

The RouterLinkStubDirective contributes substantively to the test

testing/router-stubs.ts (RouterLinkStubDirective)

@Directive({ selector: '[routerLink]', host: { '(click)': 'onClick()' } }) export class RouterLinkStubDirective { @Input('routerLink') linkParams: any; navigatedTo: any = null; onClick() { this.navigatedTo = this.linkParams; } }

The host metadata property wires the click event of the host element (the <a>) to the directive's onClick method. The URL bound to the [routerLink] attribute flows to the directive's linkParams property. Clicking the anchor should trigger the onClick method which sets the telltale navigatedTo property. Tests can inspect that property to confirm the expected click-to-navigation behavior.

By.directive and injected directives

A little more setup triggers the initial data binding and gets references to the navigation links:

app/app.component.spec.ts (test setup)

beforeEach(() => { // trigger initial data binding fixture.detectChanges(); // find DebugElements with an attached RouterLinkStubDirective linkDes = fixture.debugElement .queryAll(By.directive(RouterLinkStubDirective)); // get the attached link directive instances using the DebugElement injectors links = linkDes .map(de => de.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective); });

Two points of special interest:

  1. You can locate elements by directive, using By.directive, not just by css selectors.

  2. You can use the component's dependency injector to get an attached directive because Angular always adds attached directives to the component's injector.

Here are some tests that leverage this setup:

app/app.component.spec.ts (selected tests)

it('can get RouterLinks from template', () => { expect(links.length).toBe(3, 'should have 3 links'); expect(links[0].linkParams).toBe('/dashboard', '1st link should go to Dashboard'); expect(links[1].linkParams).toBe('/heroes', '1st link should go to Heroes'); }); it('can click Heroes link in template', () => { const heroesLinkDe = linkDes[1]; const heroesLink = links[1]; expect(heroesLink.navigatedTo).toBeNull('link should not have navigated yet'); heroesLinkDe.triggerEventHandler('click', null); fixture.detectChanges(); expect(heroesLink.navigatedTo).toBe('/heroes'); }); }

The "click" test in this example is worthless. It works hard to appear useful when in fact it tests the RouterLinkStubDirective rather than the component. This is a common failing of directive stubs.

It has a legitimate purpose in this chapter. It demonstrates how to find a RouterLink element, click it, and inspect a result, without engaging the full router machinery. This is a skill you may need to test a more sophisticated component, one that changes the display, re-calculates parameters, or re-arranges navigation options when the user clicks the link.

What good are these tests?

Stubbed RouterLink tests can confirm that a component with links and an outlet is setup properly, that the component has the links it should have, and that they are all pointing in the expected direction. These tests do not concern whether the app will succeed in navigating to the target component when the user clicks a link.

Stubbing the RouterLink and RouterOutlet is the best option for such limited testing goals. Relying on the real router would make them brittle. They could fail for reasons unrelated to the component. For example, a navigation guard could prevent an unauthorized user from visiting the HeroListComponent. That's not the fault of the AppComponent and no change to that component could cure the failed test.

A different battery of tests can explore whether the application navigates as expected in the presence of conditions that influence guards such as whether the user is authenticated and authorized. A future chapter update will explain how to write such tests with the RouterTestingModule.

Back to top

"Shallow component tests" with NO_ERRORS_SCHEMA

The previous setup declared the BannerComponent and stubbed two other components for no reason other than to avoid a compiler error.

Without them, the Angular compiler doesn't recognize the <app-banner>, <app-welcome> and <router-outlet> tags in the app.component.html template and throws an error.

Add NO_ERRORS_SCHEMA to the testing module's schemas metadata to tell the compiler to ignore unrecognized elements and attributes. You no longer have to declare irrelevant components and directives.

These tests are shallow because they only "go deep" into the components you want to test. Here is a setup (with import statements) that demonstrates the improved simplicity of shallow tests, relative to the stubbing setup.

import { NO_ERRORS_SCHEMA } from '@angular/core'; import { AppComponent } from './app.component'; import { RouterOutletStubComponent } from '../testing'; beforeEach( async(() => { TestBed.configureTestingModule({ declarations: [ AppComponent, RouterLinkStubDirective ], schemas: [ NO_ERRORS_SCHEMA ] }) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); })); import { Component } from '@angular/core'; import { AppComponent } from './app.component'; import { BannerComponent } from './banner.component'; import { RouterLinkStubDirective } from '../testing'; import { RouterOutletStubComponent } from '../testing'; @Component({selector: 'app-welcome', template: ''}) class WelcomeStubComponent {} beforeEach( async(() => { TestBed.configureTestingModule({ declarations: [ AppComponent, BannerComponent, WelcomeStubComponent, RouterLinkStubDirective, RouterOutletStubComponent ] }) .compileComponents() .then(() => { fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; }); }));

The only declarations are the component-under-test (AppComponent) and the RouterLinkStubDirective that contributes actively to the tests. The tests in this example are unchanged.

Shallow component tests with NO_ERRORS_SCHEMA greatly simplify unit testing of complex templates. However, the compiler no longer alerts you to mistakes such as misspelled or misused components and directives.

Back to top

Test an attribute directive

An attribute directive modifies the behavior of an element, component or another directive. Its name reflects the way the directive is applied: as an attribute on a host element.

The sample application's HighlightDirective sets the background color of an element based on either a data bound color or a default color (lightgray). It also sets a custom property of the element (customProperty) to true for no reason other than to show that it can.

app/shared/highlight.directive.ts

import { Directive, ElementRef, Input, OnChanges, Renderer } from '@angular/core'; @Directive({ selector: '[highlight]' }) /** Set backgroundColor for the attached element to highlight color * and set the element's customProperty to true */ export class HighlightDirective implements OnChanges { defaultColor = 'rgb(211, 211, 211)'; // lightgray @Input('highlight') bgColor: string; constructor(private renderer: Renderer, private el: ElementRef) { renderer.setElementProperty(el.nativeElement, 'customProperty', true); } ngOnChanges() { this.renderer.setElementStyle( this.el.nativeElement, 'backgroundColor', this.bgColor || this.defaultColor ); } }

It's used throughout the application, perhaps most simply in the AboutComponent:

app/about.component.ts

import { Component } from '@angular/core'; @Component({ template: ` <h2 highlight="skyblue">About</h2> <twain-quote></twain-quote> <p>All about this sample</p>` }) export class AboutComponent { }

Testing the specific use of the HighlightDirective within the AboutComponent requires only the techniques explored above (in particular the "Shallow test" approach).

app/about.component.spec.ts

beforeEach(() => { fixture = TestBed.configureTestingModule({ declarations: [ AboutComponent, HighlightDirective], schemas: [ NO_ERRORS_SCHEMA ] }) .createComponent(AboutComponent); fixture.detectChanges(); // initial binding }); it('should have skyblue <h2>', () => { const de = fixture.debugElement.query(By.css('h2')); expect(de.styles['backgroundColor']).toBe('skyblue'); });

However, testing a single use case is unlikely to explore the full range of a directive's capabilities. Finding and testing all components that use the directive is tedious, brittle, and almost as unlikely to afford full coverage.

Isolated unit tests might be helpful. But attribute directives like this one tend to manipulate the DOM. Isolated unit tests don't and therefore don't inspire confidence in the directive's efficacy.

A better solution is to create an artificial test component that demonstrates all ways to apply the directive.

app/shared/highlight.directive.spec.ts (TestComponent)

@Component({ template: ` <h2 highlight="yellow">Something Yellow</h2> <h2 highlight>The Default (Gray)</h2> <h2>No Highlight</h2> <input #box [highlight]="box.value" value="cyan"/>` }) class TestComponent { }
HighlightDirective spec in action

The <input> case binds the HighlightDirective to the name of a color value in the input box. The initial value is the word "cyan" which should be the background color of the input box.

Here are some tests of this component:

app/shared/highlight.directive.spec.ts (selected tests)

beforeEach(() => { fixture = TestBed.configureTestingModule({ declarations: [ HighlightDirective, TestComponent ] }) .createComponent(TestComponent); fixture.detectChanges(); // initial binding // all elements with an attached HighlightDirective des = fixture.debugElement.queryAll(By.directive(HighlightDirective)); // the h2 without the HighlightDirective bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])')); }); // color tests it('should have three highlighted elements', () => { expect(des.length).toBe(3); }); it('should color 1st <h2> background "yellow"', () => { expect(des[0].styles['backgroundColor']).toBe('yellow'); }); it('should color 2nd <h2> background w/ default color', () => { const dir = des[1].injector.get(HighlightDirective) as HighlightDirective; expect(des[1].styles['backgroundColor']).toBe(dir.defaultColor); }); it('should bind <input> background to value color', () => { // easier to work with nativeElement const input = des[2].nativeElement as HTMLInputElement; expect(input.style.backgroundColor).toBe('cyan', 'initial backgroundColor'); // dispatch a DOM event so that Angular responds to the input value change. input.value = 'green'; input.dispatchEvent(newEvent('input')); fixture.detectChanges(); expect(input.style.backgroundColor).toBe('green', 'changed backgroundColor'); }); // customProperty tests it('all highlighted elements should have a true customProperty', () => { const allTrue = des.map(de => !!de.properties['customProperty']).every(v => v === true); expect(allTrue).toBe(true); }); it('bare <h2> should not have a customProperty', () => { expect(bareH2.properties['customProperty']).toBeUndefined(); });

A few techniques are noteworthy:

Back to top

Isolated Unit Tests

Testing applications with the help of the Angular testing utilities is the main focus of this chapter.

However, it's often more productive to explore the inner logic of application classes with isolated unit tests that don't depend upon Angular. Such tests are often smaller and easier to read, write and maintain.

They don't

They do

Write both kinds of tests

Good developers write both kinds of tests for the same application part, often in the same spec file. Write simple isolated unit tests to validate the part in isolation. Write Angular tests to validate the part as it interacts with Angular, updates the DOM, and collaborates with the rest of the application.

Services

Services are good candidates for isolated unit testing. Here are some synchronous and asynchronous unit tests of the FancyService written without assistance from Angular testing utilities.

app/bag/bag.no-testbed.spec.ts

// Straight Jasmine - no imports from Angular test libraries describe('FancyService without the TestBed', () => { let service: FancyService; beforeEach(() => { service = new FancyService(); }); it('#getValue should return real value', () => { expect(service.getValue()).toBe('real value'); }); it('#getAsyncValue should return async value', done => { service.getAsyncValue().then(value => { expect(value).toBe('async value'); done(); }); }); it('#getTimeoutValue should return timeout value', done => { service = new FancyService(); service.getTimeoutValue().then(value => { expect(value).toBe('timeout value'); done(); }); }); it('#getObservableValue should return observable value', done => { service.getObservableValue().subscribe(value => { expect(value).toBe('observable value'); done(); }); }); });

A rough line count suggests that these isolated unit tests are about 25% smaller than equivalent Angular tests. That's telling but not decisive. The benefit comes from reduced setup and code complexity.

Compare these equivalent tests of FancyService.getTimeoutValue.

it('#getTimeoutValue should return timeout value', done => { service = new FancyService(); service.getTimeoutValue().then(value => { expect(value).toBe('timeout value'); done(); }); }); beforeEach(() => { TestBed.configureTestingModule({ providers: [FancyService] }); }); it('test should wait for FancyService.getTimeoutValue', async(inject([FancyService], (service: FancyService) => { service.getTimeoutValue().then( value => expect(value).toBe('timeout value') ); })));

They have about the same line-count. But the Angular-dependent version has more moving parts, including a couple of utility functions (async and inject). Both approaches work and it's not much of an issue if you're using the Angular testing utilities nearby for other reasons. On the other hand, why burden simple service tests with added complexity?

Pick the approach that suits you.

Services with dependencies

Services often depend on other services that Angular injects into the constructor. You can test these services without the testbed. In many cases, it's easier to create and inject dependencies by hand.

The DependentService is a simple example

app/bag/bag.ts

@Injectable() export class DependentService { constructor(private dependentService: FancyService) { } getValue() { return this.dependentService.getValue(); } }

It delegates it's only method, getValue, to the injected FancyService.

Here are several ways to test it.

app/bag/bag.no-testbed.spec.ts

describe('DependentService without the TestBed', () => { let service: DependentService; it('#getValue should return real value by way of the real FancyService', () => { service = new DependentService(new FancyService()); expect(service.getValue()).toBe('real value'); }); it('#getValue should return faked value by way of a fakeService', () => { service = new DependentService(new FakeFancyService()); expect(service.getValue()).toBe('faked value'); }); it('#getValue should return faked value from a fake object', () => { const fake = { getValue: () => 'fake value' }; service = new DependentService(fake as FancyService); expect(service.getValue()).toBe('fake value'); }); it('#getValue should return stubbed value from a FancyService spy', () => { const fancy = new FancyService(); const stubValue = 'stub value'; const spy = spyOn(fancy, 'getValue').and.returnValue(stubValue); service = new DependentService(fancy); expect(service.getValue()).toBe(stubValue, 'service returned stub value'); expect(spy.calls.count()).toBe(1, 'stubbed method was called once'); expect(spy.calls.mostRecent().returnValue).toBe(stubValue); }); });

The first test creates a FancyService with new and passes it to the DependentService constructor.

It's rarely that simple. The injected service can be difficult to create or control. You can mock the dependency, or use a dummy value, or stub the pertinent service method with a substitute method that is easy to control.

These isolated unit testing techniques are great for exploring the inner logic of a service or its simple integration with a component class. Use the Angular testing utilities when writing tests that validate how a service interacts with components within the Angular runtime environment.

Pipes

Pipes are easy to test without the Angular testing utilities.

A pipe class has one method, transform, that turns an input to an output. The transform implementation rarely interacts with the DOM. Most pipes have no dependence on Angular other than the @Pipe metadata and an interface.

Consider a TitleCasePipe that capitalizes the first letter of each word. Here's a naive implementation implemented with a regular expression.

app/shared/title-case.pipe.ts

import { Pipe, PipeTransform } from '@angular/core'; @Pipe({name: 'titlecase', pure: false}) /** Transform to Title Case: uppercase the first letter of the words in a string.*/ export class TitleCasePipe implements PipeTransform { transform(input: string): string { return input.length === 0 ? '' : input.replace(/\w\S*/g, (txt => txt[0].toUpperCase() + txt.substr(1).toLowerCase() )); } }

Anything that uses a regular expression is worth testing thoroughly. Use simple Jasmine to explore the expected cases and the edge cases.

app/shared/title-case.pipe.spec.ts

describe('TitleCasePipe', () => { // This pipe is a pure, stateless function so no need for BeforeEach let pipe = new TitleCasePipe(); it('transforms "abc" to "Abc"', () => { expect(pipe.transform('abc')).toBe('Abc'); }); it('transforms "abc def" to "Abc Def"', () => { expect(pipe.transform('abc def')).toBe('Abc Def'); }); // ... more tests ... });

Write Angular tests too

These are tests of the pipe in isolation. They can't tell if the TitleCasePipe is working properly as applied in the application components.

Consider adding component tests such as this one:

app/hero/hero-detail.component.spec.ts (pipe test)

it('should convert hero name to Title Case', fakeAsync(() => { const inputName = 'quick BROWN fox'; const titleCaseName = 'Quick Brown Fox'; // simulate user entering new name into the input box page.nameInput.value = inputName; // dispatch a DOM event so that Angular learns of input value change. page.nameInput.dispatchEvent(newEvent('input')); // Tell Angular to update the output span through the title pipe fixture.detectChanges(); expect(page.nameDisplay.textContent).toBe(titleCaseName); }));

Components

Component tests typically examine how a component class interacts with its own template or with collaborating components. The Angular testing utilities are specifically designed to facilitate such tests.

Consider this ButtonComp component.

app/bag/bag.ts (ButtonComp)

@Component({ selector: 'button-comp', template: ` <button (click)="clicked()">Click me!</button> <span>{{message}}</span>` }) export class ButtonComponent { isOn = false; clicked() { this.isOn = !this.isOn; } get message() { return `The light is ${this.isOn ? 'On' : 'Off'}`; } }

The following Angular test demonstrates that clicking a button in the template leads to an update of the on-screen message.

app/bag/bag.spec.ts (ButtonComp)

it('should support clicking a button', () => { const fixture = TestBed.createComponent(ButtonComponent); const btn = fixture.debugElement.query(By.css('button')); const span = fixture.debugElement.query(By.css('span')).nativeElement; fixture.detectChanges(); expect(span.textContent).toMatch(/is off/i, 'before click'); click(btn); fixture.detectChanges(); expect(span.textContent).toMatch(/is on/i, 'after click'); });

The assertions verify the data binding flow from one HTML control (the <button>) to the component and from the component back to a different HTML control (the <span>). A passing test means the component and its template are wired up correctly.

Isolated unit tests can more rapidly probe a component at its API boundary, exploring many more conditions with less effort.

Here are a set of unit tests that verify the component's outputs in the face of a variety of component inputs.

app/bag/bag.no-testbed.spec.ts (ButtonComp)

describe('ButtonComp', () => { let comp: ButtonComponent; beforeEach(() => comp = new ButtonComponent()); it('#isOn should be false initially', () => { expect(comp.isOn).toBe(false); }); it('#clicked() should set #isOn to true', () => { comp.clicked(); expect(comp.isOn).toBe(true); }); it('#clicked() should set #message to "is on"', () => { comp.clicked(); expect(comp.message).toMatch(/is on/i); }); it('#clicked() should toggle #isOn', () => { comp.clicked(); expect(comp.isOn).toBe(true); comp.clicked(); expect(comp.isOn).toBe(false); }); });

Isolated component tests offer a lot of test coverage with less code and almost no setup. This advantage is even more pronounced with complex components that may require meticulous preparation with the Angular testing utilities.

On the other hand, isolated unit tests can't confirm that the ButtonComp is properly bound to its template or even data bound at all. Use Angular tests for that.

Back to top

Angular Testing Utility APIs

This section takes inventory of the most useful Angular testing features and summarizes what they do.

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:

FunctionDescription
async

Runs the body of a test (it) or setup (beforeEach) function within a special async test zone. See discussion above.

fakeAsync

Runs the body of a test (it) within a special fakeAsync test zone, enabling a linear control flow coding style. See discussion above.

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 the specified number of milliseconds, clearing asynchronous activities scheduled within that timeframe. See discussion bove.

inject

Injects one or more services from the current TestBed injector into a test function. See above.

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 setting the default auto-changeDetect from its default of false. See 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 first a little at a time. Read the early part of this chapter 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 overide method takes a MetadataOverride<T> where T is the kind of metadata appropriate to the method, the parameter of an @NgModule, @Component, @Directive, or @Pipe.

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

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

Internally, all static methods cover methods of the current runtime TestBed instance that 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.

MethodsDescription
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 (e.g. DebugDomRender) 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 above.

Once called, the TestBed configuration is frozen for the duration of the current spec.

createComponent

Create an instance of a component of type T based on the current TestBed configuration. Once called, 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.

get

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.get method takes an optional second parameter, the object to return if Angular can't find the provider (null in this example):

service = TestBed.get(FancyService, null);

Once called, 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.

This method may be called exactly once. Call resetTestEnvironment first if you absolutely need to change this default in the middle of your test run.

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.

PropertiesDescription
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 or the component's change detection is under your programmatic control.

ComponentFixture methods

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

Here are the most useful methods for testers.

MethodsDescription
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 checkNoChangesafterwards to confirm there are no circular updates unless called as detectChanges(false);

autoDetectChanges

Set whether the fixture should try to detect changes automatically.

When autodetect is true, the test fixture listens for zone events and calls detectChanges. You probably still have to call fixture.detectChanges to trigger data binding updates when your test code modifies component property values directly.

The default is false and testers who prefer fine control over test behavior tend to keep it false.

Calls detectChanges immediately which detects existing changes and will trigger ngOnInit if the component has not yet been initialized.

checkNoChanges

Do a change detection run to make sure there are no pending changes. Throws an exceptions if there are.

isStable

Return true if the fixture is currently stable. Returns false if there are async tasks that have not completed.

whenStable

Returns a promise that resolves when the fixture is stable.

Hook that promise to resume testing after completion of asynchronous activity or asynchronous change detection. See above

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 sub-trees.

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

MemberDescription
nativeElement

The corresponding DOM element in the browser (null for WebWorkers).

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 with in *ngFor, the context is an NgForRow 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 above.

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 (e.g. #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":

// Filter for DebugElements with a #content reference const contentRefs = el.queryAll( de => de.references['content']);

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

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

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

Many custom application directives inject the Renderer and call one of its set... methods.

The test environment substitutes the DebugDomRender for the runtime Renderer. The DebugDomRender updates additional dictionary properties of the DebugElement when something calls a set... method.

These dictionary properties are primarily of interest to authors of Angular DOM inspection tools but they may provide useful insights to testers as well.

DictionaryDescription
properties

Updated by Renderer.setElementProperty. Many Angular directives call it, including NgModel.

attributes

Updated by Renderer.setElementAttribute. Angular [attribute] bindings call it.

classes

Updated by Renderer.setElementClass. Angular [class] bindings call it.

styles

Updated by Renderer.setElementStyle. Angular [style] bindings call it.

Here's an example of Renderer tests from the live "Specs Bag" sample.

it('DebugDomRender 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).toBe(comp, 'context is the parent component'); expect(el.attributes['account']).toBe(childComp.id, 'account attribute'); expect(el.attributes['bank']).toBe(childComp.bank, 'bank attribute'); expect(el.classes['closed']).toBe(true, 'closed class'); expect(el.classes['open']).toBe(false, 'open class'); expect(el.properties['customProperty']).toBe(true, 'customProperty'); expect(el.styles['color']).toBe(comp.color, 'color style'); expect(el.styles['width']).toBe(comp.width + 'px', 'width style'); });
Back to top

FAQ: Frequently Asked Questions

Why put specs next to the things they test?

We recommend putting unit test spec files in the same folder as the application source code files that they test because

When would I put specs in a test folder?

Application integration specs can test the interactions of multiple parts spread across folders and modules. They don't really belong to part in particular so they don't have a natural home next to any one file.

It's often better to create an appropriate folder for them in the tests directory.

Of course specs that test the test helpers belong in the test folder, next to their corresponding helper files.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.