diff --git a/public/docs/ts/latest/guide/_data.json b/public/docs/ts/latest/guide/_data.json index 2b87139b4c..b3d738527e 100644 --- a/public/docs/ts/latest/guide/_data.json +++ b/public/docs/ts/latest/guide/_data.json @@ -184,7 +184,7 @@ "testing": { "title": "Testing", - "intro": "Techniques and practices for testing an Angular app" + "intro": "Techniques and practices for testing an Angular app." }, "typescript-configuration": { diff --git a/public/docs/ts/latest/guide/attribute-directives.jade b/public/docs/ts/latest/guide/attribute-directives.jade index 7c19be8d57..94b27598eb 100644 --- a/public/docs/ts/latest/guide/attribute-directives.jade +++ b/public/docs/ts/latest/guide/attribute-directives.jade @@ -24,7 +24,7 @@ a#directive-overview ## Directives overview There are three kinds of directives in Angular: - + 1. Components — directives with a template. 1. Structural directives — change the DOM layout by adding and removing DOM elements. 1. Attribute directives — change the appearance or behavior of an element, component, or another directive. @@ -32,11 +32,11 @@ a#directive-overview *Components* are the most common of the three directives. You saw a component for the first time in the [QuickStart](../quickstart.html) example. - *Structural Directives* change the structure of the view. + *Structural Directives* change the structure of the view. Two examples are [NgFor](template-syntax.html#ngFor) and [NgIf](template-syntax.html#ngIf). Learn about them in the [Structural Directives](structural-directives.html) guide. - *Attribute directives* are used as attributes of elements. + *Attribute directives* are used as attributes of elements. The built-in [NgStyle](template-syntax.html#ngStyle) directive in the [Template Syntax](template-syntax.html) guide, for example, can change several element styles at the same time. @@ -151,12 +151,12 @@ figure.image-display :marked Angular detects that you're trying to bind to *something* but it can't find this directive - in the module's `declarations` array. - After specifying `HighlightDirective` in the `declarations` array, + in the module's `declarations` array. + After specifying `HighlightDirective` in the `declarations` array, Angular knows it can apply the directive to components declared in this module. :marked - To summarize, Angular found the `myHighlight` attribute on the `

` element. + To summarize, Angular found the `myHighlight` attribute on the `

` element. It created an instance of the `HighlightDirective` class and injected a reference to the `

` element into the directive's constructor which sets the `

` element's background style to yellow. @@ -220,8 +220,8 @@ a#input ### Binding to an _@Input_ property Notice the `@Input` !{_decorator}. It adds metadata to the class that makes the directive's `highlightColor` property available for binding. - - It's called an *input* property because data flows from the binding expression _into_ the directive. + + It's called an *input* property because data flows from the binding expression _into_ the directive. Without that input metadata, Angular rejects the binding; see [below](#why-input "Why add @Input?") for more about that. Try it by adding the following directive binding variations to the `AppComponent` template: @@ -239,7 +239,7 @@ a#input +makeExample('attribute-directives/ts/src/app/app.component.html','color') :marked - The `[myHighlight]` attribute binding both applies the highlighting directive to the `

` element + The `[myHighlight]` attribute binding both applies the highlighting directive to the `

` element and sets the directive's highlight color with a property binding. You're re-using the directive's attribute selector (`[myHighlight]`) to do both jobs. That's a crisp, compact syntax. @@ -254,8 +254,8 @@ a#input-alias ### Bind to an _@Input_ alias Fortunately you can name the directive property whatever you want _and_ **_alias it_** for binding purposes. - - Restore the original property name and specify the selector as the alias in the argument to `@Input`. + + Restore the original property name and specify the selector as the alias in the argument to `@Input`. +makeExcerpt('src/app/highlight.directive.ts', 'color', 'src/app/highlight.directive.ts (color property with alias') :marked @@ -266,7 +266,7 @@ a#input-alias +makeExample('attribute-directives/ts/src/app/app.component.html','color') :marked - Now that you're binding to `highlightColor`, modify the `onMouseEnter()` method to use it. + Now that you're binding to `highlightColor`, modify the `onMouseEnter()` method to use it. If someone neglects to bind to `highlightColor`, highlight in "red" by default. +makeExample('attribute-directives/ts/src/app/highlight.directive.3.ts', 'mouse-enter', 'src/app/highlight.directive.ts (mouse enter)')(format='.') @@ -280,7 +280,7 @@ a#input-alias It may be difficult to imagine how this directive actually works. In this section, you'll turn `AppComponent` into a harness that lets you pick the highlight color with a radio button and bind your color choice to the directive. - + Update `app.component.html` as follows: +makeExcerpt('attribute-directives/ts/src/app/app.component.html', 'v2', '') @@ -300,7 +300,7 @@ a#second-property ## Bind to a second property This highlight directive has a single customizable property. In a real app, it may need more. - At the moment, the default color — the color that prevails until the user picks a highlight color — + At the moment, the default color — the color that prevails until the user picks a highlight color — is hard-coded as "red". Let the template developer set the default color. Add a second **input** property to `HighlightDirective` called `defaultColor`: @@ -313,11 +313,11 @@ a#second-property How do you bind to a second property when you're already binding to the `myHighlight` attribute name? As with components, you can add as many directive property bindings as you need by stringing them along in the template. - The developer should be able to write the following template HTML to both bind to the `AppComponent.color` + The developer should be able to write the following template HTML to both bind to the `AppComponent.color` and fall back to "violet" as the default color. +makeExample('attribute-directives/ts/src/app/app.component.html', 'defaultColor')(format=".") :marked - Angular knows that the `defaultColor` binding belongs to the `HighlightDirective` + Angular knows that the `defaultColor` binding belongs to the `HighlightDirective` because you made it _public_ with the `@Input` !{_decorator}. Here's how the harness should work when you're done coding. @@ -368,11 +368,11 @@ a#why-input +makeExample('attribute-directives/ts/src/app/highlight.directive.ts','color') :marked - Either way, the `@Input` !{_decorator} tells Angular that this property is + Either way, the `@Input` !{_decorator} tells Angular that this property is _public_ and available for binding by a parent component. Without `@Input`, Angular refuses to bind to the property. - You've bound template HTML to component properties before and never used `@Input`. + You've bound template HTML to component properties before and never used `@Input`. What's different? The difference is a matter of trust. @@ -384,9 +384,9 @@ a#why-input But a component or directive shouldn't blindly trust _other_ components and directives. The properties of a component or directive are hidden from binding by default. They are _private_ from an Angular binding perspective. - When adorned with the `@Input` !{_decorator}, the property becomes _public_ from an Angular binding perspective. + When adorned with the `@Input` !{_decorator}, the property becomes _public_ from an Angular binding perspective. Only then can it be bound by some other component or directive. - + You can tell if `@Input` is needed by the position of the property name in a binding. * When it appears in the template expression to the ***right*** of the equals (=), @@ -399,7 +399,7 @@ a#why-input Now apply that reasoning to the following example: +makeExample('attribute-directives/ts/src/app/app.component.html','color')(format=".") :marked - * The `color` property in the expression on the right belongs to the template's component. + * The `color` property in the expression on the right belongs to the template's component. The template and its component trust each other. The `color` property doesn't require the `@Input` !{_decorator}. diff --git a/public/docs/ts/latest/guide/testing.jade b/public/docs/ts/latest/guide/testing.jade index 2ad8ddeca8..aa1b782da6 100644 --- a/public/docs/ts/latest/guide/testing.jade +++ b/public/docs/ts/latest/guide/testing.jade @@ -7,79 +7,94 @@ block includes - var __objectAsMap = 'object'; :marked - This guide 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 + This guide offers tips and techniques for testing Angular applications. + Though this page includes some general testing principles and techniques, + the focus is on testing applications written with Angular. a#top :marked - # Table of Contents - * [Live Examples](#live-examples "Live examples of the tests in this guide") + # Contents + * [Live examples](#live-examples "Live examples of the tests in this guide")

- * [Introduction to Angular Testing](#testing-intro) + * [Introduction to Angular testing](#testing-intro) - [Tools and technologies](#tools-and-tech) - [Setup](#setup) - - [Isolated tests vs. Angular testing utilities](#isolated-v-testing-utilities) - * [The first test](#1st-karma-test) - - [run with karma](#run-karma) - - [debugging karma tests](#test-debugging) - * [A simple component test](#simple-component-test) + - [Isolated unit tests vs. the Angular testing utilities](#isolated-v-testing-utilities) + * [The first karma test](#1st-karma-test) + - [Run with karma](#run-karma) + - [Test debugging](#test-debugging) + - [Try the live example](#live-karma-example) + * [Test a component](#simple-component-test) - [_TestBed_](#testbed) - - [_configureTestingModule_](#configure-testing-module) - [_createComponent_](#create-component) - - [_ComponentFixture_, _DebugElement_, _query(By.css)_](#component-fixture) - - [_detectChanges_](#detect-changes) - - [_ComponentFixtureAutoDetect _](#auto-detect-changes) + - [_ComponentFixture_, _DebugElement_, and _query(By.css)_](#component-fixture) + - [The tests](#the-tests) + - [_detectChanges_: Angular change detection within a test](#detect-changes) + - [Try the live example](#try-example) + - [Automatic change detection](#auto-detect-changes) * [Test a component with an external template](#component-with-external-template) - - [_async_ setup in _beforeEach_](#async-in-before-each) - - [_compileComponents_](#compile-components) + - [The first asynchronous _beforeEach_](#async-in-before-each) + - [_compileComponents_](#compile-components) + - [The second synchronous _beforeEach_](#second-before-each) + - [Waiting for _compileComponents_](#waiting-compile-components) + - [Try the live example](#live-external-template-example) * [Test a component with a service dependency](#component-with-dependency) - - [test doubles](#service-test-doubles) - - [get the injected service](#get-injected-service) + - [Provide service test doubles](#service-test-doubles) + - [Get injected services](#get-injected-service) - [_TestBed.get_](#testbed-get) + - [Always get the service from an injector](#service-from-injector) + - [Final setup and tests](#welcome-spec-setup) * [Test a component with an async service](#component-with-async-service) - - [spies](#service-spy) - - [_async_ test](#async) + - [Spying on the real service](#service-spy) + - [Synchronous tests](#sync-tests) + - [The _async_ funciton in it](#async) - [_whenStable_](#when-stable) - - [_fakeAsync_](#fake-async) - - [_tick_](#tick) + - [The _fakeAsync_ function](#fake-async) + - [The _tick_ function](#tick) - [_jasmine.done_](#jasmine-done) * [Test a component with inputs and outputs](#component-with-input-output) + - [Test _DashboardHeroComponent_ stand-alone](#dashboard-standalone) - [_triggerEventHandler_](#trigger-event-handler) * [Test a component inside a test host component](#component-inside-test-host) -

+

* [Test a routed component](#routed-component) - - [The _inject_ helper](#inject) + - [The _inject_ helper function](#inject) - [Test a routed component with parameters](#routed-component-w-param) - [Create an _Observable_ test double](#stub-observable) - [Testing with the _Observable_ test double](#tests-w-observable-double) * [Use a _page_ object to simplify setup](#page-object) - * [Setup with module imports](#import-module) + * [Set up with module imports](#import-module) + * [Import the feature module](#feature-module-import)

* [Override a component's providers](#component-override) - - [_overrideComponent_](#override-component-method) - - [Provide a _spy-stub_](#spy-stub) + - [The _overrideComponent_ method](#override-component-method) + - [Provide a _spy-stub (HeroDetailServiceSpy)_](#spy-stub) + - [The override tests](#override-tests) + - [More overrides](#more-overrides) * [Test a _RouterOutlet_ component](#router-outlet-component) - - [stubbing unneeded components](#stub-component) + - [Stubbing unneeded components](#stub-component) - [Stubbing the _RouterLink_](#router-link-stub) - [_By.directive_ and injected directives](#by-directive) - * ["Shallow" component tests with *NO\_ERRORS\_SCHEMA*](#shallow-component-test) -

+ - [What good are these tests?](#why-stubbed-routerlink-tests) + * ["Shallow component tests" with *NO\_ERRORS\_SCHEMA*](#shallow-component-test) +

* [Test an attribute directive](#attribute-directive)

* [Isolated unit tests](#isolated-unit-tests "Unit testing without the Angular testing utilities") - [Services](#isolated-service-tests) + - [Services with dependencies](#services-with-dependencies) - [Pipes](#isolated-pipe-tests) + - [Write Angular tests too](#write-tests) - [Components](#isolated-component-tests) * [Angular testing utility APIs](#atu-apis) - - [Stand-alone functions](#atu-apis): `async`, `fakeAsync`, etc. - - [_TestBed_](#testbed-class-summary) - - [_ComponentFixture_](#component-fixture-api-summary) + - [_TestBed_ class summary](#testbed-class-summary) + - [The _ComponentFixture_](#component-fixture-api-summary) + - [_ComponentFixture_ properties](#component-fixture-properties) + - [The _ComponentFixture_ methods](#component-fixture-methods) - [_DebugElement_](#debug-element-details) - * [Test environment setup](#setup) - - [setup files](#setup-files): `karma.conf`, `karma-test-shim`, `systemjs.config` - - [npm packages](#npm-packages) - * [FAQ](#faq "Frequently asked questions") + * [Test environment setup files](#setup-files) + - [npm packages](#npm-packages) + * [FAQ: Frequently asked questions](#faq "Frequently asked questions") :marked It’s a big agenda. Fortunately, you can learn a little bit at a time and put each lesson to use. @@ -88,13 +103,13 @@ a#top This guide presents tests of a sample application that is much like the [_Tour of Heroes_ tutorial](../tutorial). The sample application and all tests in this guide are available as live examples for inspection, experiment, and download: - * A spec to verify the test environment - * The first component spec with inline template - * A component spec with external template - * The QuickStart seed's AppComponent spec - * The sample application to be tested - * All specs that test the sample application - * A grab bag of additional specs + * A spec to verify the test environment. + * The first component spec with inline template. + * A component spec with external template. + * The QuickStart seed's AppComponent spec. + * The sample application to be tested. + * All specs that test the sample application. + * A grab bag of additional specs. a(href="#top").to-top Back to top .l-hr @@ -102,15 +117,17 @@ a#testing-intro :marked ## Introduction to Angular Testing - You write tests to explore and confirm the behavior of the application. + This page guides you through writing tests to explore + and confirm the behavior of the application. Testing + does the following: - 1. They **guard** against changes that break existing code (“regressions”). + 1. Guards against changes that break existing code (“regressions”). - 1. They **clarify** what the code does both when used as intended and when faced with deviant conditions. + 1. Clarifies what the code does both when used as intended and when faced with deviant conditions. - 1. 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, + 1. Reveals 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. -a#tools-and-tech +a#tools-and-tech :marked - ### Tools and Technologies + ### Tools and technologies - You can write and run Angular tests with a variety of tools and technologies. + You can write and run Angular tests with a variety of tools and technologies. This guide describes specific choices that are known to work well. - + table(width="100%") col(width="20%") - col(width="80%") + col(width="80%") tr th Technology th Purpose @@ -137,25 +154,25 @@ table(width="100%") td(style="vertical-align: top") Jasmine td :marked - The [Jasmine test framework](http://jasmine.github.io/2.4/introduction.html). - provides everything needed to write basic tests. + The [Jasmine test framework](http://jasmine.github.io/2.4/introduction.html) + provides everything needed to write basic tests. It ships with an HTML test runner that executes tests in the browser. tr(style=top) - td(style="vertical-align: top") Angular Testing Utilities + td(style="vertical-align: top") Angular testing utilities td :marked - 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 + 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. tr(style=top) td(style="vertical-align: top") Karma td :marked The [karma test runner](https://karma-runner.github.io/1.0/index.html) - is ideal for writing and running unit tests while developing the application. + 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 guide describes how to setup and run tests with karma. + This guide describes how to set up and run tests with karma. tr(style=top) td(style="vertical-align: top") Protractor td @@ -163,8 +180,8 @@ table(width="100%") 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. + and a second process runs protractor tests that simulate user behavior + and assert that the application respond in the browser as expected. a#setup :marked @@ -173,39 +190,39 @@ a#setup There are two fast paths to getting started with unit testing. 1. Start a new project following the instructions in [Setup](setup.html "Setup"). - 1. Start a new project with the + 1. 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 + 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 guide, the application and its tests are based on the [setup instructions](setup.html "Setup"). + In this guide, the application and its tests are based on the [setup instructions](setup.html "Setup"). For a discussion of the unit testing setup files, [see below](#setup-files). a#isolated-v-testing-utilities :marked - ### Isolated unit tests vs Angular testing utilites + ### Isolated unit tests vs. the Angular testing utilites - [Isolated unit tests](#isolated-unit-tests "Unit testing without the Angular testing utilities") - 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 + [Isolated unit tests](#isolated-unit-tests "Unit testing without the Angular testing utilities") + 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.* - - Components can be tested in isolation as well. - However, isolated unit tests don't reveal how components interact with Angular. + + *You should write isolated unit tests for pipes and services.* + + You can test components in isolation as well. + However, isolated unit tests don't reveal how components 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**. The Angular testing utilities include the `TestBed` class and several helper functions from `@angular/core/testing`. - They are the main focus of this guide and you'll start learning about them + They are the main focus of this guide and you'll learn about them when you write your [first component test](#simple-component-test). - A comprehensive review of the Angular testing utilities appears [later in the guide](#atu-apis). + A comprehensive review of the Angular testing utilities appears [later in this guide](#atu-apis). - But first you should write a dummy test to verify that your test environment is setup properly + But first you should write a dummy test to verify that your test environment is set up properly and to lock in a few basic testing skills. a(href="#top").to-top Back to top @@ -216,14 +233,14 @@ a#1st-karma-test :marked ## The first karma test - Start with a simple test to make sure the setup works properly. + Start with a simple test to make sure that the setup works properly. Create a new file called `1st.spec.ts` in the application root folder, `src/app/` .alert.is-important :marked - Tests written in Jasmine are called _specs_ . - **The filename extension must be `.spec.ts`**, + 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. :marked @@ -236,8 +253,8 @@ a#1st-karma-test a#run-karma :marked - ### Run karma - Compile and run it in karma from the command line with this command: + ### Run with karma + Compile and run it in karma from the command line using the following command: code-example(format="." language="bash"). npm test :marked @@ -252,19 +269,20 @@ code-example(format="." language="bash"). figure.image-display img(src='/resources/images/devguide/testing/karma-browser.png' style="width:400px;" alt="Karma browser") :marked - Hide (don't close!) the browser and focus on the console output which should look something like this. + Hide (don't close!) the browser and focus on the console output, which + should look something like this: code-example(format="." language="bash"). > npm test ... [0] 1:37:03 PM - Compilation complete. Watching for file changes. ... - [1] Chrome 51.0.2704: Executed 0 of 0 SUCCESS + [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) :marked - Both the compiler and karma continue to run. The compiler output is preceeded by `[0]`; + Both the compiler and karma continue to run. The compiler output is preceded by `[0]`; the karma output by `[1]`. Change the expectation from `true` to `false`. @@ -283,7 +301,7 @@ code-example(format="." language="bash"). [1] Chrome 51.0.2704: Executed 1 of 1 (1 FAILED) (0.005 secs / 0.005 secs) :marked - It failed of course. + It fails of course. Restore the expectation from `false` back to `true`. Both processes detect the change, re-run, and karma reports complete success. @@ -291,25 +309,26 @@ code-example(format="." language="bash"). .alert.is-helpful :marked The console log can be quite long. Keep your eye on the last line. - It says `SUCCESS` when all is well. + When all is well, it reads `SUCCESS`. -a#test-debugging +a#test-debugging :marked ### Test debugging - - Debug specs in the browser in the same way you debug an application. - - - Reveal the karma browser window (hidden earlier). - - Click the "DEBUG" button; it opens a new browser tab and re-runs the tests - - Open the browser's “Developer Tools” (F12 or Ctrl-Shift-I). - - Pick the "sources" section - - Open the `1st.spec.ts` test file (Ctrl-P, then start typing the name of the file). - - Set a breakpoint in the test - - Refresh the browser … and it stops at the breakpoint. - + + Debug specs in the browser in the same way that you debug an application. + + 1. Reveal the karma browser window (hidden earlier). + 1. Click the **DEBUG** button; it opens a new browser tab and re-runs the tests. + 1. Open the browser's “Developer Tools” (`Ctrl-Shift-I` on windows; `Command-Option-I` in OSX). + 1. Pick the "sources" section. + 1. Open the `1st.spec.ts` test file (Control/Command-P, then start typing the name of the file). + 1. Set a breakpoint in the test. + 1. Refresh the browser, and it stops at the breakpoint. + figure.image-display img(src='/resources/images/devguide/testing/karma-1st-spec-debug.png' style="width:700px;" alt="Karma debugging") +a#live-karma-example :marked ### Try the live example @@ -325,7 +344,7 @@ a#simple-component-test ## Test a component An Angular component is the first thing most developers want to test. - The `BannerComponent` in `src/app/banner-inline.component.ts` is the simplest component in this application and + The `BannerComponent` in `src/app/banner-inline.component.ts` is the simplest component in this application and a good place to start. It presents the application title at the top of the screen within an `

` tag. +makeExample('testing/ts/src/app/banner-inline.component.ts', '', 'src/app/banner-inline.component.ts')(format='.') @@ -335,7 +354,8 @@ a#simple-component-test it's perfect for a first encounter with the Angular testing utilities. The corresponding `src/app/banner-inline.component.spec.ts` sits in the same folder as the component, - for reasons explained [here](#q-spec-file-location); + for reasons explained in the [FAQ](#faq) answer to + ["Why put specs next to the things they test?"](#q-spec-file-location). Start with ES6 import statements to get access to symbols referenced in the spec. +makeExample('testing/ts/src/app/banner-inline.component.spec.ts', 'imports', 'src/app/banner-inline.component.spec.ts (imports)')(format='.') @@ -347,24 +367,26 @@ a#configure-testing-module a#testbed :marked - ### The _TestBed_ + ### _TestBed_ - The `TestBed` is the first and most important of the Angular testing utilities. - It creates an Angular testing module — an `@NgModule` class — - that you configure with the `configureTestingModule` method to produce the module environment for the class you want to test. + `TestBed` is the first and most important of the Angular testing utilities. + It creates an Angular testing module—an `@NgModule` class—that + you configure with the `configureTestingModule` method to produce the module environment for the class you want to test. In effect, you detach the tested component from its own application module - and re-attach it to a dynamically-constructed, Angular test module + and re-attach it to a dynamically-constructed Angular test module tailored specifically for this battery of tests. - The `configureTestingModule` method takes an `@NgModule`-like metadata object. + The `configureTestingModule` method takes an `@NgModule`-like metadata object. The metadata object can have most of the properties of a normal [Angular module](ngmodule.html). - + _This metadata object_ simply declares the component to test, `BannerComponent`. The metadata lack `imports` because (a) the default testing module configuration already has what `BannerComponent` needs and (b) `BannerComponent` doesn't interact with any other components. - Call `configureTestingModule` within a `beforeEach` so that, - before each spec runs, the `TestBed` can reset itself to a base state. + + Call `configureTestingModule` within a `beforeEach` so that + `TestBed` can reset itself to a base state before each test runs. + 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. @@ -374,61 +396,64 @@ a#testbed to something like the `BrowserModule` from `@angular/platform-browser`. :marked This default configuration is merely a _foundation_ for testing an app. - Later you'll call `TestBed.configureTestingModule` with more metadata that defines additional - imports, declarations, providers and schemas to fit your application tests. - Optional `override...` methods can fine-tune aspects of the configuration. + Later you'll call `TestBed.configureTestingModule` with more metadata that define additional + imports, declarations, providers, and schemas to fit your application tests. + Optional `override` methods can fine-tune aspects of the configuration. a#create-component :marked ### _createComponent_ - After configuring the `TestBed`, you tell it to create an instance of the _component-under-test_. - In this example, `TestBed.createComponent` creates an instance of `BannerComponent` and + After configuring `TestBed`, you tell it to create an instance of the _component-under-test_. + In this example, `TestBed.createComponent` creates an instance of `BannerComponent` and returns a [_component test fixture_](#component-fixture). .alert.is-important :marked - Do not re-configure the `TestBed` after calling `createComponent`. + Do not re-configure `TestBed` after calling `createComponent`. :marked The `createComponent` method closes the current `TestBed` instance to further configuration. - You cannot call any more `TestBed` configuration methods, not `configureTestingModule` - nor any of the `override...` methods. The `TestBed` throws an error if you try. + You cannot call any more `TestBed` configuration methods, not `configureTestingModule` + nor any of the `override...` methods. If you try, `TestBed` throws an error. a#component-fixture :marked ### _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 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 `

` tags. + The `title` property value is interpolated into the DOM within `

` tags. Use the fixture's `DebugElement` to `query` for the `

` 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 **`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. .l-sub-section :marked The `queryAll` method returns an array of _all_ `DebugElements` that satisfy the predicate. - - A _predicate_ is a function that returns a boolean. + + 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. - + :marked - The **`By`** class is an Angular testing utility that produces useful predicates. - Its `By.css` static method produces a + 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 assert that `el` contains the expected title text. +a#the-tests +:marked ### The tests + Jasmine runs the `beforeEach` function before each of these tests +makeExample('testing/ts/src/app/banner-inline.component.spec.ts', 'tests', 'src/app/banner-inline.component.spec.ts (tests)')(format='.') + :marked These tests ask the `DebugElement` for the native HTML element to satisfy their expectations. @@ -456,6 +481,8 @@ a#detect-changes It gives the tester an opportunity to inspect or change the state of the component _before Angular initiates data binding or calls lifecycle hooks_. +a#try-example +:marked ### Try the live example Take a moment to explore this component spec as a and lock in these fundamentals of component unit testing. @@ -466,12 +493,14 @@ a#auto-detect-changes The `BannerComponent` tests frequently call `detectChanges`. Some testers prefer that the Angular test environment run change detection automatically. - That's possible by configuring the `TestBed` with the `ComponentFixtureAutoDetect` provider which - you first import from the testing utility library ... + + That's possible by configuring the `TestBed` with the `ComponentFixtureAutoDetect` provider. + First import it from the testing utility library: +makeExample('testing/ts/src/app/banner.component.detect-changes.spec.ts', 'import-ComponentFixtureAutoDetect', 'src/app/banner.component.detect-changes.spec.ts (import)')(format='.') :marked - ... and then add to the `providers` array of the testing module configuration: + Then add it to the `providers` array of the testing module configuration: +makeExample('testing/ts/src/app/banner.component.detect-changes.spec.ts', 'auto-detect', 'src/app/banner.component.detect-changes.spec.ts (AutoDetect)')(format='.') + :marked Here are three tests that illustrate how automatic change detection works. +makeExample('testing/ts/src/app/banner.component.detect-changes.spec.ts', 'auto-detect-tests', 'src/app/banner.component.detect-changes.spec.ts (AutoDetect Tests)')(format='.') @@ -497,7 +526,7 @@ a(href="#top").to-top Back to top a#component-with-external-template :marked ## Test a component with an external template - The application's actual `BannerComponent` behaves the same as the version above but is implemented differently. + The application's actual `BannerComponent` behaves the same as the version above but is implemented differently. It has _external_ template and css files, specified in `templateUrl` and `styleUrls` properties. +makeExample('testing/ts/src/app/banner.component.ts', '', 'src/app/banner.component.ts')(format='.') :marked @@ -514,21 +543,22 @@ a#component-with-external-template The test setup for `BannerComponent` must give the Angular template compiler time to read the files. The logic in the `beforeEach` of the previous spec is split into two `beforeEach` calls. - The first `beforeEach` handles asynchronous compilation. + The first `beforeEach` handles asynchronous compilation. +makeExample('testing/ts/src/app/banner.component.spec.ts', 'async-before-each', 'src/app/banner.component.spec.ts (first beforeEach)')(format='.') :marked Notice the `async` function called as the argument to `beforeEach`. - The `async` function is one of the Angular testing utilities. + The `async` function is one of the Angular testing utilities and + has to be imported. +makeExample('testing/ts/src/app/banner.component.detect-changes.spec.ts', 'import-async')(format='.') :marked It takes a parameterless function and _returns a function_ which becomes the true argument to the `beforeEach`. The body of the `async` argument looks much like the body of a synchronous `beforeEach`. - There is nothing obviously asynchronous about it. - For example, it doesn't return a promise and + 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 would be in standard Jasmine asynchronous tests. Internally, `async` arranges for the body of the `beforeEach` to run in a special _async test zone_ that hides the mechanics of asynchronous execution. @@ -542,7 +572,7 @@ a#compile-components calls to other `TestBed` static methods such as `compileComponents`. The `TestBed.compileComponents` method asynchronously compiles all the components configured in the testing module. - In this example, the `BannerComponent` is the only component to compile. + In this example, the `BannerComponent` is the only component to compile. When `compileComponents` completes, the external templates and css files have been "inlined" and `TestBed.createComponent` can create new instances of `BannerComponent` synchronously. .l-sub-section @@ -550,22 +580,24 @@ a#compile-components WebPack developers need not call `compileComponents` because it inlines templates and css as part of the automated build process that precedes running the test. :marked - In this example, `TestBed.compileComponents` only compiles the `BannerComponent`. - Tests later in the guide declare multiple components and + In this example, `TestBed.compileComponents` only compiles the `BannerComponent`. + Tests later in the guide declare multiple components and a few specs import entire application modules that hold yet more components. Any of these components might have external templates and css files. `TestBed.compileComponents` compiles all of the declared components asynchronously at one time. .alert.is-important :marked - Do not configure the `TestBed` after calling `compileComponents`. - Make `compileComponents` the last step + Do not configure the `TestBed` after calling `compileComponents`. + Make `compileComponents` the last step before calling `TestBed.createComponent` to instantiate the _component-under-test_. :marked Calling `compileComponents` closes the current `TestBed` instance is further configuration. - You cannot call any more `TestBed` configuration methods, not `configureTestingModule` + You cannot call any more `TestBed` configuration methods, not `configureTestingModule` nor any of the `override...` methods. The `TestBed` throws an error if you try. +a#second-before-each +:marked ### The second synchronous _beforeEach_ A _synchronous_ `beforeEach` containing the remaining setup steps follows the asynchronous `beforeEach`. @@ -574,19 +606,21 @@ a#compile-components These are the same steps as in the original `beforeEach`. They include creating an instance of the `BannerComponent` and querying for the elements to inspect. - You can count on the testrunner to wait for the first asynchronous `beforeEach` to finish before calling the second. + You can count on the test runner to wait for the first asynchronous `beforeEach` to finish before calling the second. +a#waiting-compile-components +:marked ### Waiting for _compileComponents_ The `compileComponents` method returns a promise so you can perform additional tasks _immediately after_ it finishes. - For example, you could move the synchronous code in the second `beforeEach` + For example, you could move the synchronous code in the second `beforeEach` into a `compileComponents().then(...)` callback and write only one `beforeEach`. Most developers find that hard to read. The two `beforeEach` calls are widely preferred. ### Try the live example - + Take a moment to explore this component spec as a . .l-sub-section @@ -594,7 +628,7 @@ a#compile-components The [Quickstart seed](setup.html) provides a similar test of its `AppComponent` as you can see in _this_ . It too calls `compileComponents` although it doesn't have to because the `AppComponent`'s template is inline. - + There's no harm in it and you might call `compileComponents` anyway in case you decide later to re-factor the template into a separate file. The tests in this guide only call `compileComponents` when necessary. @@ -607,9 +641,11 @@ a#component-with-dependency :marked ## 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`: +makeExample('testing/ts/src/app/welcome.component.ts', '', 'src/app/welcome.component.ts')(format='.') + :marked 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, `src/app/welcome.component.spec.ts`: @@ -618,20 +654,20 @@ a#component-with-dependency 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`. - -a#service-test-doubles + +a#service-test-doubles :marked ### Provide service test doubles - A _component-under-test_ doesn't have to be injected with real services. + 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. + Injecting the real `UserService` could be a nightmare. + The real service might ask the user for login credentials and + attempt to reach an authentication server. + These behaviors can 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` @@ -647,7 +683,7 @@ a#get-injected-service 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_**, + 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`. +makeExample('testing/ts/src/app/welcome.component.spec.ts', 'injected-service', 'WelcomeComponent\'s injector')(format='.') @@ -657,23 +693,26 @@ a#testbed-get ### _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. + 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, + 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: +makeExample('testing/ts/src/app/welcome.component.spec.ts', 'inject-from-testbed', 'TestBed injector')(format='.') .l-sub-section :marked The [`inject`](#inject) utility function is another way to get one or more services from the test root injector. - - See the section "[_Override a component's providers_](#component-override)" for a use case - in which `inject` and `TestBed.get` do not work and you must get the service from the component's injector. + + For a use case in which `inject` and `TestBed.get` do not work, + see the section [_Override a component's providers_](#component-override), which + explains why you must get the service from the component's injector instead. + +a#service-from-injector :marked ### 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, + Do _not_ reference the `userServiceStub` object + that's 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`. +makeExample('testing/ts/src/app/welcome.component.spec.ts', 'stub-not-injected')(format='.') @@ -689,10 +728,10 @@ a#welcome-spec-setup The first is a sanity test; it confirms that the stubbed `UserService` is called and working. .l-sub-section :marked - The second parameter to the Jasmine `it` (e.g., `'expected name'`) is an optional addendum. + 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. - + In a spec with multiple expectations, it can help clarify what went wrong and which expectation failed. + :marked 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. @@ -705,20 +744,22 @@ a(href="#top").to-top Back to top a#component-with-async-service :marked ## Test a component with an async service - Many services return values asynchronously. + 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 `src/app/shared` folder because the author intends to display Twain quotes on other pages someday. Here is the `TwainComponent`. +makeExample('testing/ts/src/app/shared/twain.component.ts', 'component', 'src/app/shared/twain.component.ts')(format='.') + :marked - 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. + The `TwainService` implementation is irrelevant for this particular test. + 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 `src/app/shared/twain.component.spec.ts` shows one way to do that: + In general, tests should not make calls to remote servers. + They should emulate such calls. The setup in this `src/app/shared/twain.component.spec.ts` shows one way to do that: +makeExample('testing/ts/src/app/shared/twain.component.spec.ts', 'setup', 'src/app/shared/twain.component.spec.ts (setup)')(format='.') a#service-spy @@ -726,17 +767,17 @@ a#service-spy ### Spying on the real service This setup is similar to the [`welcome.component.spec` setup](#welcome-spec-setup). - But instead of creating a stubbed service object, it injects the _real_ service (see the testing module `providers`) and + 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. +makeExample('testing/ts/src/app/shared/twain.component.spec.ts', 'spy')(format='.') :marked 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. + The spy bypasses the actual `getQuote` method and therefore does not contact the server. .l-sub-section :marked - 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. + 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. Spying on the real service isn't always easy, especially when the real service has injected dependencies. @@ -744,27 +785,30 @@ a#service-spy :marked Here are the tests with commentary to follow: + +makeExample('testing/ts/src/app/shared/twain.component.spec.ts', 'tests', 'src/app/shared/twain.component.spec.ts (tests)') + +a#sync-tests :marked ### Synchronous tests - The first two tests are synchronous. + 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`. + the first change detection cycle during which Angular calls `ngOnInit`. - Neither test can prove that a value from the service is be displayed. + Neither test can prove that a value from the service is 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_. a#async :marked - ## The _async_ function in _it_ + ### The _async_ function in _it_ Notice the `async` in the third test. +makeExample('testing/ts/src/app/shared/twain.component.spec.ts', 'async-test', 'src/app/shared/twain.component.spec.ts (async test)')(format='.') :marked - The `async` function is one of the Angular testing utilities. + 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_ as [discussed earlier](#async-in-before-each) when it was called in a `beforeEach`. @@ -776,27 +820,29 @@ a#async a#when-stable :marked - ## _whenStable_ + ### _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 `twainService.getQuote` + This test has no direct access to the promise returned by the call to `twainService.getQuote` because it is buried inside `TwainComponent.ngOnInit` and therefore inaccessible to a test that probes only the component API surface. - Fortunately, the `getQuote` promise is accessible to the _async test zone_ + Fortunately, the `getQuote` promise is accessible to the _async test zone_, which intercepts all promises issued within the _async_ method call _no matter where they occur_. - - 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`) + The `ComponentFixture.whenStable` method returns its own promise, which + resolves when the `getQuote` promise finishes. + 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. a#fakeAsync a#fake-async :marked - ## The _fakeAsync_ function + ### The _fakeAsync_ function The fourth test verifies the same component behavior in a different way. +makeExample('testing/ts/src/app/shared/twain.component.spec.ts', 'fake-async-test', 'src/app/shared/twain.component.spec.ts (fakeAsync test)')(format='.') @@ -805,7 +851,7 @@ a#fake-async The `fakeAsync` function is another of the Angular testing utilities. Like [async](#async), it _takes_ a parameterless function and _returns_ a function - that becomes the argument to the Jasmine `it` call. + 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_. @@ -818,41 +864,43 @@ a#fake-async There _are_ limitations. For example, you cannot make an XHR call from within a `fakeAsync`. a#tick -a#tick-first-look :marked - ## The _tick_ function + ### 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. + You can only call it within a `fakeAsync` body. - Calling `tick()` simulates the passage of time until all pending asynchronous activities complete, + Calling `tick()` simulates the passage of time until all pending asynchronous activities finish, 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. + It returns nothing. There is no promise to wait for. + Proceed with the same test code that appeared in 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, + 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. a#jasmine-done :marked - ## _jasmine.done_ + ### _jasmine.done_ + While the `async` and `fakeAsync` functions greatly + simplify Angular asynchronous testing, + you can still fall back to the traditional Jasmine asynchronous testing technique. - 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 + You can still pass `it` a function that takes a [`done` callback](http://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support). 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: +makeExample('testing/ts/src/app/shared/twain.component.spec.ts', 'done-test', 'src/app/shared/twain.component.spec.ts (done test)')(format='.') :marked - 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. + Although there is no direct access to the `getQuote` promise inside `TwainComponent`, + the spy has direct access, which makes it possible to wait for `getQuote` to finish. + + Writing test functions with `done`, while more cumbersome than `async` + and `fakeAsync`, is a viable and occasionally necessary technique. + For example, you can't call `async` or `fakeAsync` when testing + code that involves the `intervalTimer`, as is common when + testing async `Observable` methods. a(href="#top").to-top Back to top @@ -862,49 +910,53 @@ a#component-with-input-output :marked ## 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 + The host uses a property binding to set the input property and 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 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`. + 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: +makeExample('testing/ts/src/app/dashboard/dashboard.component.html', 'dashboard-hero', 'src/app/dashboard/dashboard.component.html (excerpt)')(format='.') :marked - 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. - + The `DashboardHeroComponent` appears in an `*ngFor` repeater, which sets each component's `hero` input property + to the looping value and listens for the component's `selected` event. + Here's the component's definition: +makeExample('testing/ts/src/app/dashboard/dashboard-hero.component.ts', 'component', 'src/app/dashboard/dashboard-hero.component.ts (component)')(format='.') :marked 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` - 1. Test it as a stand-alone component - 1. Test it as used by a substitute for `DashboardComponent` + You can use one of these approaches: + - Test it as used by `DashboardComponent`. + - Test it as a stand-alone component. + - Test it as used by a substitute for `DashboardComponent`. A quick look at the `DashboardComponent` constructor discourages the first approach: +makeExample('testing/ts/src/app/dashboard/dashboard.component.ts', 'ctor', 'src/app/dashboard/dashboard.component.ts (constructor)')(format='.') :marked - 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 `DashboardComponent` depends on the Angular router and the `HeroService`. + You'd probably have to replace them both with test doubles, which is a lot of work. The router seems particularly challenging. .l-sub-section :marked The [discussion below](#routed-component) covers testing components that require the router. :marked - 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. + The immediate goal is to test the `DashboardHeroComponent`, not the `DashboardComponent`, + so, try the second and third options. +a#dashboard-standalone +:marked ### Test _DashboardHeroComponent_ stand-alone + Here's the spec file setup. +makeExample('testing/ts/src/app/dashboard/dashboard-hero.component.spec.ts', 'setup', 'src/app/dashboard/dashboard-hero.component.spec.ts (setup)')(format='.') + :marked The async `beforeEach` was discussed [above](#component-with-external-template). Having compiled the components asynchronously with `compileComponents`, the rest of the setup @@ -915,17 +967,19 @@ a#component-with-input-output The first test follows: +makeExample('testing/ts/src/app/dashboard/dashboard-hero.component.spec.ts', 'name-test', 'src/app/dashboard/dashboard-hero.component.spec.ts (name test)')(format='.') + :marked - 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: + It verifies that the hero name is propagated to template with a binding. + Because the template passes the hero name through the Angular `UpperCasePipe`, + the test must match the element value with the uppercased name: +makeExample('testing/ts/src/app/dashboard/dashboard-hero.component.html')(format='.') :marked .alert.is-helpful :marked - This small test demonstrates how Angular tests can verify a component's visual representation - — something not possible with [isolated unit tests](#isolated-component-tests) — - at low cost and without resorting to much slower and more complicated end-to-end tests. + This small test demonstrates how Angular tests can verify a component's visual + representation—something not possible with + [isolated unit tests](#isolated-component-tests)—at + low cost and without resorting to much slower and more complicated end-to-end tests. :marked The second test verifies click behavior. Clicking the hero should raise a `selected` event that the @@ -934,13 +988,13 @@ a#component-with-input-output :marked 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 `
`. + The `heroEl` is a `DebugElement` that represents the hero `
`. The test calls `triggerEventHandler` with the "click" event name. - The "click" event binding responds by calling `DashboardHeroComponent.click()`. - + 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. - + a#trigger-event-handler :marked ### _triggerEventHandler_ @@ -952,28 +1006,34 @@ a#trigger-event-handler +makeExample('testing/ts/src/app/dashboard/dashboard-hero.component.spec.ts', 'trigger-event-handler')(format='.') :marked - 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. + 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 are less forgiving. For example, the `RouterLink` + directive expects an object with a `button` property + that identifies which mouse button was pressed. + This directive throws an error if the event object doesn't do this correctly. a#click-helper :marked 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: +makeExample('testing/ts/src/testing/index.ts', 'click-event', 'testing/index.ts (click helper)')(format='.') + :marked - 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 + The first parameter is the _element-to-click_. If you wish, you can pass a + custom event object as the second parameter. The default is a (partial) + left-button mouse event object accepted by many handlers including the `RouterLink` directive. .callout.is-critical header click() is not an Angular testing utility :marked - The `click()` helper function is **not** one of the Angular testing utilities. - It's a function defined in _this guide's sample code_ and used by all of the sample tests. + The `click()` helper function is **not** one of the Angular testing utilities. + It's a function defined in _this guide's sample code_. + All of the sample tests use it. If you like it, add it to your own collection of helpers. :marked Here's the previous test, rewritten using this click helper. @@ -986,21 +1046,21 @@ a#component-inside-test-host :marked ## 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? + In the previous approach, the tests themselves played the role of the host `DashboardComponent`. + But does the `DashboardHeroComponent` work correctly 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: +makeExample('testing/ts/src/app/dashboard/dashboard-hero.component.spec.ts', 'test-host', 'src/app/dashboard/dashboard-hero.component.spec.ts (test host)')(format='.') :marked 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 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. + It binds the component's `selected` event with its `onSelected` handler, + which records the emitted hero + in its `selectedHero` property. Later, the tests check that property to verify that the + `DashboardHeroComponent.selected` event emitted the right hero. The setup for the test-host tests is similar to the setup for the stand-alone tests: +makeExample('testing/ts/src/app/dashboard/dashboard-hero.component.spec.ts', 'test-host-setup', 'src/app/dashboard/dashboard-hero.component.spec.ts (test host setup)')(format='.') @@ -1009,15 +1069,16 @@ a#component-inside-test-host 1. It _declares_ both the `DashboardHeroComponent` and the `TestHostComponent`. 1. 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 + The `createComponent` returns a `fixture` that holds an instance of `TestHostComponent` instead of an instance of `DashboardHeroComponent`. + + 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 + + The tests themselves are almost identical to the stand-alone version: +makeExample('testing/ts/src/app/dashboard/dashboard-hero.component.spec.ts', 'test-host-tests', 'src/app/dashboard/dashboard-hero.component.spec.ts (test-host)')(format='.') + :marked 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. @@ -1033,11 +1094,12 @@ a#routed-component Testing the actual `DashboardComponent` seemed daunting because it injects the `Router`. +makeExample('testing/ts/src/app/dashboard/dashboard.component.ts', 'ctor', 'src/app/dashboard/dashboard.component.ts (constructor)')(format='.') :marked - It also injects the `HeroService` but faking that is a [familiar story](#component-with-async-service). - The `Router` has a complicated API and is entwined with other services and application pre-conditions. + It also injects the `HeroService`, but faking that is a [familiar story](#component-with-async-service). + The `Router` has a complicated API and is entwined with other services and application preconditions. Fortunately, the `DashboardComponent` isn't doing much with the `Router` +makeExample('testing/ts/src/app/dashboard/dashboard.component.ts', 'goto-detail', 'src/app/dashboard/dashboard.component.ts (goToDetail)')(format='.') + :marked This is often the case. As a rule you test the component, not the router, @@ -1045,13 +1107,14 @@ a#routed-component Stubbing the router with a test implementation is an easy option. This should do the trick: +makeExample('testing/ts/src/app/dashboard/dashboard.component.spec.ts', 'router-stub', 'src/app/dashboard/dashboard.component.spec.ts (Router Stub)')(format='.') :marked - Now we setup the testing module with the test stubs for the `Router` and `HeroService` and + Now set up the testing module with the test stubs for the `Router` and `HeroService`, and create a test instance of the `DashboardComponent` for subsequent testing. +makeExample('testing/ts/src/app/dashboard/dashboard.component.spec.ts', 'compile-and-create-body', 'src/app/dashboard/dashboard.component.spec.ts (compile and create)')(format='.') :marked The following test clicks the displayed hero and confirms (with the help of a spy) that `Router.navigateByUrl` is called with the expected url. +makeExample('testing/ts/src/app/dashboard/dashboard.component.spec.ts', 'navigate-test', 'src/app/dashboard/dashboard.component.spec.ts (navigate test)')(format='.') + a#inject :marked ### The _inject_ function @@ -1062,9 +1125,9 @@ a#inject 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 - 1. a test function whose parameters correspond exactly to each item in the injection token array + The `inject` function has two parameters: + 1. An array of Angular dependency injection tokens. + 1. A test function whose parameters correspond exactly to each item in the injection token array. .callout.is-important header inject uses the TestBed Injector @@ -1074,7 +1137,7 @@ a#inject :marked 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. + 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: +makeExample('testing/ts/src/app/welcome.component.spec.ts', 'injected-service', 'Component\'s injector')(format='.') @@ -1094,13 +1157,13 @@ a#inject a#routed-component-w-param :marked ### 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. + + 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`, + 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: +makeExample('testing/ts/src/app/hero/hero-detail.component.ts', 'ctor', 'src/app/hero/hero-detail.component.ts (constructor)')(format='.') @@ -1109,40 +1172,42 @@ a#routed-component-w-param +makeExample('testing/ts/src/app/hero/hero-detail.component.ts', 'ng-on-init', 'src/app/hero/hero-detail.component.ts (ngOnInit)')(format='.') .l-sub-section :marked - The subscription confirms that there are `params` and, if there are, - plucks the `id` from those `params`. - It passes the `id` to the component's `getHero` method (not shown) - which fetches a hero and sets the component's `hero` property. - The `getHero` method treats an undefined `id` or `id===0` as a request to edit a 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 subscribe 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 the`id` parameter is missing, the `pluck` operator fails and the `catch` treats failure as a request to edit a new hero. + The [Router](router.html#route-parameters) guide covers `ActivatedRoute.params` in more detail. :marked 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: + Stubbing the `ActivatedRoute` follows the same pattern except for a complication: the `ActivatedRoute.params` is an _Observable_. a#stub-observable :marked - ### _Observable_ test double + ### Create an _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 is a cross-application, re-usable _test helper class_. + Consider placing such helpers in a `testing` folder sibling to the `app` folder. This sample keeps `ActivatedRouteStub` in `testing/router-stubs.ts`: - + +makeExample('testing/ts/src/testing/router-stubs.ts', 'activated-route-stub', 'testing/router-stubs.ts (ActivatedRouteStub)')(format='.') :marked - Notable features of this stub: + Notable features of this stub are: * The stub implements only two of the `ActivatedRoute` capabilities: `params` and `snapshot.params`. - * _BehaviorSubject_ + * _BehaviorSubject_ drives the stub's `params` _Observable_ and returns the same value to every `params` subscriber until it's given a new value. - * The `HeroDetailComponent` chain its expressions to this stub `params` _Observable_ which is now under the tester's control. + * The `HeroDetailComponent` chains its expressions to this stub `params` _Observable_ which is now under the tester's control. * Setting the `testParams` property causes the `subject` to push the assigned value into `params`. That triggers the `HeroDetailComponent` _params_ subscription, described above, in the same way that navigation does. @@ -1157,7 +1222,7 @@ a#stub-observable a#tests-w-observable-double :marked - ### _Observable_ tests + ### Testing with the _Observable_ test double Here's a test demonstrating the component's behavior when the observed `id` refers to an existing hero: +makeExample('testing/ts/src/app/hero/hero-detail.component.spec.ts', 'route-good-id', 'src/app/hero/hero-detail.component.spec.ts (existing id)')(format='.') .l-sub-section @@ -1171,7 +1236,7 @@ a#tests-w-observable-double +makeExample('testing/ts/src/app/hero/hero-detail.component.spec.ts', 'route-bad-id', 'src/app/hero/hero-detail.component.spec.ts (bad id)')(format='.') :marked :marked - While this app doesn't have a route to the `HeroDetailComponent` that omits the `id` parameter, it might add such a route someday. + 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. @@ -1197,11 +1262,11 @@ figure.image-display But there's already plenty of template complexity. +makeExample('testing/ts/src/app/hero/hero-detail.component.html', '', 'src/app/hero/hero-detail.component.html')(format='.') :marked - To fully exercise the component, the test needs ... - * to wait until a `hero` arrives before `*ngIf` allows any element in DOM - * element references for the title name span and name input-box to inspect their values - * two button references to click - * spies on component and router methods + To fully exercise the component, the test needs a lot of setup: + * It must wait until a hero arrives before `*ngIf` allows any element in DOM. + * It needs references to the title `` and the name `` so it can inspect their values. + * It needs references to the two buttons so it can click them. + * It needs spies for some of the component and router methods. Even a small form such as this one can produce a mess of tortured conditional setup and CSS element selection. @@ -1211,15 +1276,19 @@ figure.image-display :marked 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. + + A `createComponent` method creates a `page` object and fills in the blanks once the `hero` arrives. +makeExample('testing/ts/src/app/hero/hero-detail.component.spec.ts', 'create-component', 'src/app/hero/hero-detail.component.spec.ts (createComponent)')(format='.') + :marked - The [observable tests](#tests-w-observable-double) in the previous section demonstrate how `createComponent` and `page` - keep the tests short and _on message_. + The [observable tests](#tests-w-observable-double) 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. +makeExample('testing/ts/src/app/hero/hero-detail.component.spec.ts', 'selected-tests', 'src/app/hero/hero-detail.component.spec.ts (selected tests)')(format='.') + a(href="#top").to-top Back to top .l-hr @@ -1234,33 +1303,39 @@ a#import-module and these must be added to the testing module too. Fortunately, the `TestBed.configureTestingModule` parameter parallels - the metadata passed to the `@NgModule` decorator + 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. + 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: - * `NgModel` and friends in the `FormsModule` enable two-way data binding - * The `TitleCasePipe` from the `shared` folder - * Router services (which these tests are stubbing) - * Hero data access services (also stubbed) + * `NgModel` and friends in the `FormsModule` to enable two-way data binding. + * The `TitleCasePipe` from the `shared` folder. + * Router services (which these tests are stubbing). + * Hero data access services (also stubbed). + One approach is to configure the testing module from the individual pieces as in this example: +makeExample('testing/ts/src/app/hero/hero-detail.component.spec.ts', 'setup-forms-module', 'src/app/hero/hero-detail.component.spec.ts (FormsModule setup)')(format='.') + :marked 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: + +makeExample('testing/ts/src/app/hero/hero-detail.component.spec.ts', 'setup-shared-module', 'src/app/hero/hero-detail.component.spec.ts (SharedModule setup)')(format='.') + :marked It's a bit tighter and smaller, with fewer import statements (not shown). a#feature-module-import :marked ### Import the feature module + The `HeroDetailComponent` is part of the `HeroModule` [Feature Module](ngmodule.html#feature-modules) that aggregates more of the interdependent pieces including the `SharedModule`. Try a test configuration that imports the `HeroModule` like this one: +makeExample('testing/ts/src/app/hero/hero-detail.component.spec.ts', 'setup-hero-module', 'src/app/hero/hero-detail.component.spec.ts (HeroModule setup)')(format='.') + :marked That's _really_ crisp. Only the _test doubles_ in the `providers` remain. Even the `HeroDetailComponent` declaration is gone. .l-sub-section @@ -1271,7 +1346,7 @@ a#feature-module-import .alert.is-helpful :marked 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. + especially when the feature module is small and mostly self-contained, as feature modules should be. :marked a(href="#top").to-top Back to top @@ -1279,16 +1354,17 @@ a(href="#top").to-top Back to top a#component-override :marked - ## Override component providers + ## Override a component's providers The `HeroDetailComponent` provides its own `HeroDetailService`. +makeExample('testing/ts/src/app/hero/hero-detail.component.ts', 'prototype', 'src/app/hero/hero-detail.component.ts (prototype)')(format='.') + :marked - It's not possible to stub the component's `HeroDetailService` in the `providers` of the `TestBed.configureTestingModule`. + 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. + 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. @@ -1303,16 +1379,18 @@ a#component-override +makeExample('testing/ts/src/app/hero/hero-detail.service.ts', 'prototype', 'src/app/hero/hero-detail.service.ts (prototype)')(format='.') :marked - The [previous test configuration](#feature-module-import) replaces the real `HeroService` with a `FakeHeroService` + The [previous test configuration](#feature-module-import) replaces the real `HeroService` with a `FakeHeroService` that intercepts server requests and fakes their responses. :marked - What if you aren't so lucky. What if faking the `HeroService` is hard? - What if `HeroDetailService` makes its own server requests? - + 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: + +makeExample('testing/ts/src/app/hero/hero-detail.component.spec.ts', 'setup-override', 'src/app/hero/hero-detail.component.spec.ts (Override setup)')(format='.') + :marked Notice that `TestBed.configureTestingModule` no longer provides a (fake) `HeroService` because it's [not needed](#spy-stub). @@ -1349,28 +1427,34 @@ a#spy-stub ### Provide a _spy stub_ (_HeroDetailServiceSpy_) This example completely replaces the component's `providers` array with a new array containing a `HeroDetailServiceSpy`. - - The `HeroDetailServiceSpy` is a stubbed version of the real `HeroDetailService` - that fakes all necessary features of that service. - It neither injects nor delegates to the lower level `HeroService` + + The `HeroDetailServiceSpy` is a stubbed version of the real `HeroDetailService` + that fakes all necessary features of that service. + It neither injects nor delegates to the lower level `HeroService` so there's no need to provide a test double for that. - The related `HeroDetailComponent` tests will assert that methods of the `HeroDetailService` + The related `HeroDetailComponent` tests will assert that methods of the `HeroDetailService` were called by spying on the service methods. Accordingly, the stub implements its methods as spies: + +makeExample('testing/ts/src/app/hero/hero-detail.component.spec.ts', 'hds-spy', 'src/app/hero/hero-detail.component.spec.ts (HeroDetailServiceSpy)')(format='.') + +a#override-tests :marked ### The override tests Now the tests can control the component's hero directly by manipulating the spy-stub's `testHero` and confirm that service methods were called. +makeExample('testing/ts/src/app/hero/hero-detail.component.spec.ts', 'override-tests', 'src/app/hero/hero-detail.component.spec.ts (override tests)')(format='.') + +a#more-overrides + :marked ### 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. a(href="#top").to-top Back to top @@ -1396,31 +1480,35 @@ a#stub-component :marked ### Stubbing unneeded components - The test setup should look familiar + The test setup should look familiar. + +makeExample('testing/ts/src/app/app.component.spec.ts', 'setup-stubs', 'src/app/app.component.spec.ts (Stub Setup)')(format='.') + :marked The `AppComponent` is the declared test subject. + The setup extends the default testing module with one real component (`BannerComponent`) and several stubs. * `BannerComponent` is simple and harmless to use as is. * The real `WelcomeComponent` has an injected service. `WelcomeStubComponent` is a placeholder with no service to worry about. - * The real `RouterOutlet` is complex and errors easily. + * The real `RouterOutlet` is complex and errors easily. The `RouterOutletStubComponent` (in `testing/router-stubs.ts`) is safely inert. - - The component stubs are essential. - Without them, the Angular compiler doesn't recognize the `` and `` tags - and throws an error. + + The component stubs are essential. + Without them, the Angular compiler doesn't recognize the `` and `` tags + and throws an error. a#router-link-stub :marked ### Stubbing the _RouterLink_ The `RouterLinkStubDirective` contributes substantively to the test: + +makeExample('testing/ts/src/testing/router-stubs.ts', 'router-link', 'testing/router-stubs.ts (RouterLinkStubDirective)')(format='.') :marked The `host` metadata property wires the click event of the host element (the ``) to the directive's `onClick` method. - The URL bound to the `[routerLink]` attribute flows to the directive's `linkParams` property. + 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. @@ -1436,7 +1524,7 @@ a#inject-directive 1. You can locate elements _by directive_, using `By.directive`, not just by css selectors. - 1. You can use the component's dependency injector to get an attached directive because + 1. You can use the component's dependency injector to get an attached directive because Angular always adds attached directives to the component's injector. a#app-component-tests @@ -1446,11 +1534,11 @@ a#app-component-tests .l-sub-section :marked The "click" test _in this example_ is worthless. - It works hard to appear useful when in fact it + 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 guide. + + It has a legitimate purpose in this guide. 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, @@ -1466,13 +1554,18 @@ a#why-stubbed-routerlink-tests 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. + 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 guide update will explain how to write such tests with the `RouterTestingModule`. + +.alert.is-helpful + :marked + A future guide update will explain how to write such + tests with the `RouterTestingModule`. + a(href="#top").to-top Back to top .l-hr @@ -1480,30 +1573,31 @@ a#shallow-component-test :marked ## "Shallow component tests" with *NO\_ERRORS\_SCHEMA* - The [previous setup](#stub-component) declared the `BannerComponent` and stubbed two other components + The [previous setup](#stub-component) 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 ``, `` and `` tags - in the [_app.component.html_](#app-component-html) template and throws an error. + + Without them, the Angular compiler doesn't recognize the ``, `` and `` tags + in the [_app.component.html_](#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. -+makeTabs('testing/ts/src/app/app.component.spec.ts, testing/ts/src/app/app.component.spec.ts', - 'setup-schemas, setup-stubs-w-imports', + + Here is a setup, with `import` statements, that demonstrates the improved simplicity of _shallow_ tests, relative to the stubbing setup. ++makeTabs('testing/ts/src/app/app.component.spec.ts, testing/ts/src/app/app.component.spec.ts', + 'setup-schemas, setup-stubs-w-imports', 'src/app/app.component.spec.ts (NO_ERRORS_SCHEMA), src/app/app.component.spec.ts (Stubs)')(format='.') :marked - The _only_ declarations are the _component-under-test_ (`AppComponent`) and the `RouterLinkStubDirective` + The _only_ declarations are the _component-under-test_ (`AppComponent`) and the `RouterLinkStubDirective` that contributes actively to the tests. The [tests in this example](#app-component-tests) are unchanged. .alert.is-important :marked _Shallow component tests_ with `NO_ERRORS_SCHEMA` greatly simplify unit testing of complex templates. - However, the compiler no longer alerts you to mistakes + However, the compiler no longer alerts you to mistakes such as misspelled or misused components and directives. a(href="#top").to-top Back to top @@ -1519,7 +1613,7 @@ a#attribute-directive 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` + It also sets a custom property of the element (`customProperty`) to `true` for no reason other than to show that it can. +makeExample('testing/ts/src/app/shared/highlight.directive.ts', '', 'src/app/shared/highlight.directive.ts')(format='.') :marked @@ -1533,12 +1627,15 @@ a#attribute-directive 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](#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. + [Isolated unit tests](#isolated-unit-tests) might be helpful, + but attribute directives like this one tend to manipulate the DOM. + Isolated unit tests don't touch the DOM and, therefore, + do not 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. + +makeExample('testing/ts/src/app/shared/highlight.directive.spec.ts', 'test-component', 'src/app/shared/highlight.directive.spec.ts (TestComponent)')(format='.') + figure.image-display img(src='/resources/images/devguide/testing/highlight-directive-spec.png' width="200px" alt="HighlightDirective spec in action") .l-sub-section @@ -1553,7 +1650,7 @@ figure.image-display * The `By.directive` predicate is a great way to get the elements that have this directive _when their element types are unknown_. - * The `:not` pseudo-class + * The `:not` pseudo-class in `By.css('h2:not([highlight])')` helps find `

` elements that _do not_ have the directive. `By.css('*:not([highlight])')` finds _any_ element that does not have the directive. @@ -1562,11 +1659,11 @@ figure.image-display But feel free to exploit the `nativeElement` when that seems easier or more clear than the abstraction. :marked - * Angular adds a directive to the injector of the element to which it is applied. - The test for the default color uses the injector of the 2nd `

` to get its `HighlightDirective` instance + * Angular adds a directive to the injector of the element to which it is applied. + The test for the default color uses the injector of the second `

` to get its `HighlightDirective` instance and its `defaultColor`. -// Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? +// Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? * `DebugElement.properties` affords access to the artificial custom property that is set by the directive. a(href="#top").to-top Back to top @@ -1581,18 +1678,18 @@ a#isolated-unit-tests 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. + Such tests are often smaller and easier to read, write, and maintain. - They don't - * import from the Angular test libraries - * configure a module - * prepare dependency injection `providers` - * call `inject` or `async` or `fakeAsync` + They don't carry extra baggage: + * Import from the Angular test libraries. + * Configure a module. + * Prepare dependency injection `providers`. + * Call `inject` or `async` or `fakeAsync`. - They do - * exhibit standard, Angular-agnostic testing techniques - * create instances directly with `new` - * substitute test doubles (stubs, spys, and mocks) for the real dependencies. + They follow patterns familiar to test developers everywhere: + * Exhibit standard, Angular-agnostic testing techniques. + * Create instances directly with `new`. + * Substitute test doubles (stubs, spys, and mocks) for the real dependencies. .callout.is-important header Write both kinds of tests @@ -1605,48 +1702,52 @@ a#isolated-unit-tests a#isolated-service-tests :marked ### Services - Services are good candidates for isolated unit testing. - Here are some synchronous and asynchronous unit tests of the `FancyService` + 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. +makeExample('testing/ts/src/app/bag/bag.no-testbed.spec.ts', 'FancyService', 'src/app/bag/bag.no-testbed.spec.ts') :marked - A rough line count suggests that these isolated unit tests are about 25% smaller than equivalent Angular tests. - That's telling but not decisive. + 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`. +makeTabs( - `testing/ts/src/app/bag/bag.no-testbed.spec.ts, testing/ts/src/app/bag/bag.spec.ts`, - 'getTimeoutValue, getTimeoutValue', + `testing/ts/src/app/bag/bag.no-testbed.spec.ts, testing/ts/src/app/bag/bag.spec.ts`, + 'getTimeoutValue, getTimeoutValue', `src/app/bag/bag.no-testbed.spec.ts (Isolated), src/app/bag/bag.spec.ts (with Angular testing utilities)`) :marked - 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. + 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 +a#services-with-dependencies +:marked + ### Services with dependencies Services often depend on other services that Angular injects into the constructor. - You can test these services _without_ the testbed. + 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 + The `DependentService` is a simple example: +makeExample('testing/ts/src/app/bag/bag.ts', 'DependentService', 'src/app/bag/bag.ts')(format='.') + :marked - It delegates it's only method, `getValue`, to the injected `FancyService`. + It delegates its only method, `getValue`, to the injected `FancyService`. Here are several ways to test it. +makeExample('testing/ts/src/app/bag/bag.no-testbed.spec.ts', 'DependentService', 'src/app/bag/bag.no-testbed.spec.ts') :marked 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. + + However, it's rarely that simple. The injected service can be difficult to create or control. + You can mock the dependency, use a dummy value, or stub the pertinent service method + with a substitute method that's 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. @@ -1658,20 +1759,24 @@ a#isolated-pipe-tests ### 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. + A pipe class has one method, `transform`, that manipulates the input + value into a transformed output value. 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. + Here's a naive implementation with a regular expression. +makeExample('testing/ts/src/app/shared/title-case.pipe.ts', '', 'src/app/shared/title-case.pipe.ts')(format='.') :marked Anything that uses a regular expression is worth testing thoroughly. Use simple Jasmine to explore the expected cases and the edge cases. + +makeExample('testing/ts/src/app/shared/title-case.pipe.spec.ts', 'excerpt', 'src/app/shared/title-case.pipe.spec.ts') + +a#write-tests :marked - #### Write Angular tests too + ### 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. @@ -1691,10 +1796,11 @@ a#isolated-component-tests The following Angular test demonstrates that clicking a button in the template leads to an update of the on-screen message. +makeExample('testing/ts/src/app/bag/bag.spec.ts', 'ButtonComp', 'src/app/bag/bag.spec.ts (ButtonComp)')(format='.') + :marked - The assertions verify the data binding flow from one HTML control (the `