2016-09-13 17:39:39 -04:00
|
|
|
|
block includes
|
|
|
|
|
include ../_util-fns
|
|
|
|
|
- var _JavaScript = 'JavaScript';
|
|
|
|
|
//- Double underscore means don't escape var, use !{__var}.
|
|
|
|
|
- var __chaining_op = '<code>;</code> or <code>,</code>';
|
|
|
|
|
- var __new_op = '<code>new</code>';
|
|
|
|
|
- var __objectAsMap = 'object';
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
This chapter offers tips and techniques for testing Angular applications.
|
|
|
|
|
Along the way you will learn some general testing principles and techniques but the focus is on
|
|
|
|
|
Angular testing.
|
|
|
|
|
|
|
|
|
|
a#top
|
|
|
|
|
:marked
|
|
|
|
|
# Contents
|
|
|
|
|
|
|
|
|
|
* [Introduction to Angular Testing](#testing-101)
|
|
|
|
|
* [Setup](#setup)
|
|
|
|
|
* [The first karma test](#1st-karma-test)
|
|
|
|
|
* [The Angular Testing Platform (ATP) ](#atp-intro)
|
|
|
|
|
* [The sample application and its tests](#sample-app)
|
|
|
|
|
* [A simple component test](#simple-component-test)
|
|
|
|
|
* [Test a component with a service dependency](#component-with-dependency)
|
|
|
|
|
* [Test a component with an async service](#component-with-async-service)
|
|
|
|
|
* [Test a component with an external template](#component-with-external-template)
|
|
|
|
|
* [Test a component with inputs and outputs](#component-with-inputs-output)
|
|
|
|
|
* [Test a component inside a test host component](#component-inside-test-host)
|
|
|
|
|
* [Test a routed component](#routed-component)
|
|
|
|
|
* [Isolated tests](#testing-without-atp "Testing without the Angular Testing Platform")
|
|
|
|
|
* [_TestBed_ API](#atp-api)
|
|
|
|
|
* [FAQ](#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.
|
|
|
|
|
|
|
|
|
|
# Live examples
|
|
|
|
|
The chapter sample code is available as live examples for inspection, experiment, and download.
|
|
|
|
|
|
|
|
|
|
* <live-example>The sample application</live-example>
|
|
|
|
|
* <live-example plnkr="1st-specs">The first spec</live-example>
|
|
|
|
|
* <live-example plnkr="app-specs">The complete application specs</live-example>
|
|
|
|
|
* <live-example plnkr="bag-specs">A grab bag of demonstration specs</live-example>
|
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
|
|
|
|
|
.l-hr
|
|
|
|
|
a#testing-101
|
|
|
|
|
:marked
|
|
|
|
|
# Introduction to Angular Testing
|
|
|
|
|
|
|
|
|
|
You write tests to explore and confirm the behavior of the application.
|
|
|
|
|
|
|
|
|
|
1. They **guard** against changes that break existing code (“regressions”).
|
|
|
|
|
|
|
|
|
|
1. They **clarify** 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,
|
|
|
|
|
something to cure now rather than later when it becomes expensive to fix.
|
|
|
|
|
|
|
|
|
|
This chapter assumes that you know something about testing. Don't worry if you don't.
|
|
|
|
|
There are plenty of books and online resources to get up to speed.
|
|
|
|
|
|
|
|
|
|
<!-- TODO
|
|
|
|
|
:marked
|
|
|
|
|
## Learn more
|
|
|
|
|
Learn more about basic Jasmine testing here
|
|
|
|
|
[Resources TBD](./#)
|
|
|
|
|
-->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Tools and Technologies
|
|
|
|
|
|
|
|
|
|
You can write and run Angular tests with a variety of tools and technologies.
|
|
|
|
|
This chapter describes specific choices that are known to work well.
|
|
|
|
|
|
|
|
|
|
table(width="100%")
|
|
|
|
|
col(width="20%")
|
|
|
|
|
col(width="80%")
|
|
|
|
|
tr
|
|
|
|
|
th Technology
|
|
|
|
|
th Purpose
|
|
|
|
|
tr(style=top)
|
|
|
|
|
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.
|
|
|
|
|
It ships with an HTML test runner that executes tests in the browser.
|
|
|
|
|
tr(style=top)
|
|
|
|
|
td(style="vertical-align: top") Angular Testing Platform
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The Angular Testing Platform creates a test environment and harness
|
|
|
|
|
for the application code under test.
|
|
|
|
|
Use it 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 tests while developing the application.
|
|
|
|
|
It can be an integral part of the application build process.
|
|
|
|
|
This chapter describes how to setup and run tests with karma.
|
|
|
|
|
tr(style=top)
|
|
|
|
|
td(style="vertical-align: top") Protractor
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
.l-hr
|
|
|
|
|
a#setup
|
|
|
|
|
:marked
|
|
|
|
|
# Setup
|
|
|
|
|
|
|
|
|
|
Many think writing tests is fun.
|
|
|
|
|
Few enjoy setting up the test environment.
|
|
|
|
|
To get to the fun as quickly as possible,
|
|
|
|
|
the deep details of setup appear later in the chapter (_forthcoming_).
|
|
|
|
|
A bare minimum of discussion plus the downloadable source code must suffice for now.
|
|
|
|
|
|
|
|
|
|
There are two fast paths to getting started.
|
|
|
|
|
1. Start a new project following the instructions in the
|
|
|
|
|
[QuickStart github repository](https://github.com/angular/quickstart/blob/master/README.md).
|
|
|
|
|
|
|
|
|
|
1. Start a new project with the
|
|
|
|
|
[Angular CLI](https://github.com/angular/angular-cli/blob/master/README.md).
|
|
|
|
|
|
|
|
|
|
Both approaches install **npm packages, files, and scripts** pre-configured for applications
|
|
|
|
|
built in their respective modalities.
|
|
|
|
|
Their artifacts and procedures differ slightly but their essentials are the same
|
|
|
|
|
and there are no differences in the test code.
|
|
|
|
|
|
|
|
|
|
In this chapter, the application and its tests are based on the QuickStart repo.
|
|
|
|
|
|
|
|
|
|
.alert.is-helpful
|
|
|
|
|
:marked
|
|
|
|
|
If youur application was based on the QuickStart repository,
|
|
|
|
|
you can skip the rest of this section and get on with your first test.
|
|
|
|
|
The QuickStart repo provides all necessary setup.
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
Here's brief description of the setup files.
|
|
|
|
|
|
|
|
|
|
table(width="100%")
|
|
|
|
|
col(width="20%")
|
|
|
|
|
col(width="80%")
|
|
|
|
|
tr
|
|
|
|
|
th File
|
|
|
|
|
th Description
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>karma.conf.js</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The karma configuration file that specifies which plug-ins to use,
|
|
|
|
|
which application and test files to load, which browser(s) to use,
|
|
|
|
|
and how to report test results.
|
|
|
|
|
|
|
|
|
|
It loads three other setup files:
|
|
|
|
|
* `systemjs.config.js`
|
|
|
|
|
* `systemjs.config.extras.js`
|
|
|
|
|
* `karma-test-shim.js`
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>karma-test-shim.js</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
This shim prepares karma specifically for the Angular test environment
|
|
|
|
|
and launches karma itself.
|
|
|
|
|
It loads the `systemjs.config.js` file as part of that process.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>systemjs.config.js</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
[SystemJS](https://github.com/systemjs/systemjs/blob/master/README.md)
|
|
|
|
|
loads the application and test modules.
|
|
|
|
|
This script tells SystemJS where to find the module files and how to load them.
|
|
|
|
|
It's the same version of the file used by QuickStart-based applications.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>systemjs.config.extras.js</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
An optional file that supplements the SystemJS configuration in `systemjs.config.js` with
|
|
|
|
|
configuration for the specific needs of the application itself.
|
|
|
|
|
|
|
|
|
|
A stock `systemjs.config.js` can't anticipate those needs.
|
|
|
|
|
You fill the gaps here.
|
|
|
|
|
|
|
|
|
|
The sample version for this chapter adds the **model barrel**
|
|
|
|
|
to the SystemJs `packages` configuration.
|
|
|
|
|
tr
|
|
|
|
|
td(colspan="2")
|
|
|
|
|
+makeExample('testing/ts/systemjs.config.extras.js', '', 'systemjs.config.extras.js')(format='.')
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
### npm packages
|
|
|
|
|
|
|
|
|
|
The sample tests are written to run in Jasmine and karma.
|
|
|
|
|
The two "fast path" setups added the appropriate Jasmine and karma npm packages to the
|
|
|
|
|
`devDependencies` section of the `package.json`.
|
|
|
|
|
They were installed when you ran `npm install`.
|
|
|
|
|
|
|
|
|
|
.l-hr
|
|
|
|
|
a#1st-karma-test
|
|
|
|
|
:marked
|
|
|
|
|
# The first karma test
|
|
|
|
|
|
|
|
|
|
Start with a simple test to make sure the setup works properly.
|
|
|
|
|
|
|
|
|
|
Create a new file called `1st.spec.ts` in the application root folder, `app/`
|
|
|
|
|
|
2016-08-09 12:38:25 -04:00
|
|
|
|
.alert.is-important
|
|
|
|
|
:marked
|
2016-09-13 17:39:39 -04:00
|
|
|
|
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
|
|
|
|
|
**Put spec files somewhere within the `app/` folder.**
|
|
|
|
|
The `karma.conf.js` tells karma to look for spec files there,
|
|
|
|
|
for reasons explained [below](#spec-file-location).
|
|
|
|
|
|
|
|
|
|
Add the following code to `app/1st.spec.ts`.
|
|
|
|
|
+makeExample('testing/ts/app/1st.spec.ts', '', 'app/1st.spec.ts')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
## Run karma
|
|
|
|
|
Compile and run it in karma from the command line.
|
|
|
|
|
|
|
|
|
|
.l-sub-section
|
|
|
|
|
:marked
|
|
|
|
|
The QuickStart repo adds the following command to the `scripts` section in `package.json`.
|
|
|
|
|
|
|
|
|
|
code-example(format="." language="bash").
|
|
|
|
|
"test": "tsc && concurrently \"tsc -w\" \"karma start karma.conf.js\"",
|
|
|
|
|
:marked
|
|
|
|
|
Add that to your `package.json` if it's not there already.
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
Open a terminal or command window and enter
|
|
|
|
|
code-example(format="." language="bash").
|
|
|
|
|
npm test
|
|
|
|
|
:marked
|
|
|
|
|
The command compiles the application and test code a first time.
|
|
|
|
|
If the compile fails, the command aborts.
|
|
|
|
|
|
|
|
|
|
If it succeeds, the command re-compiles (this time in watch mode) in one process
|
|
|
|
|
and starts karma in another.
|
|
|
|
|
Both processes watch pertinent files and re-run when they detect changes.
|
|
|
|
|
|
|
|
|
|
After a few moments, karma opens a browser ...
|
|
|
|
|
figure.image-display
|
|
|
|
|
img(src='/resources/images/devguide/testing/karma-browser.png' style="width:400px;" alt="Karma browser")
|
|
|
|
|
:marked
|
|
|
|
|
... and starts writing to the console.
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
> tsc && concurrently "tsc -w" "karma start karma.conf.js"
|
|
|
|
|
|
|
|
|
|
[0] 1:37:03 PM - Compilation complete. Watching for file changes.
|
|
|
|
|
[1] 24 07 2016 13:37:09.310:WARN [karma]: No captured browser, open http://localhost:9876/
|
|
|
|
|
[1] 24 07 2016 13:37:09.361:INFO [karma]: Karma v0.13.22 server started at http://localhost:9876/
|
|
|
|
|
[1] 24 07 2016 13:37:09.370:INFO [launcher]: Starting browser Chrome
|
|
|
|
|
[1] 24 07 2016 13:37:10.974:INFO [Chrome 51.0.2704]: Connected on socket /#Cf6A5PkvMzjbbtn1AAAA with id 24600087
|
|
|
|
|
[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)
|
2016-08-09 12:38:25 -04:00
|
|
|
|
|
2016-02-06 02:27:06 -05:00
|
|
|
|
:marked
|
2016-09-13 17:39:39 -04:00
|
|
|
|
Both the compiler and karma continue to run. The compiler output is preceeded by `[0]`;
|
|
|
|
|
the karma output by `[1]`.
|
|
|
|
|
|
|
|
|
|
Change the expectation from `true` to `false`.
|
|
|
|
|
|
|
|
|
|
The _compiler_ watcher detects the change and recompiles.
|
2016-02-06 02:27:06 -05:00
|
|
|
|
|
2016-09-13 17:39:39 -04:00
|
|
|
|
code-example(format="." language="bash").
|
|
|
|
|
[0] 1:49:21 PM - File change detected. Starting incremental compilation...
|
|
|
|
|
[0] 1:49:25 PM - Compilation complete. Watching for file changes.
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
The _karma_ watcher detects the change to the compilation output and re-runs the test.
|
|
|
|
|
code-example(format="." language="bash").
|
|
|
|
|
[1] Chrome 51.0.2704: Executed 0 of 1 SUCCESS
|
|
|
|
|
Chrome 51.0.2704 1st tests true is true FAILED
|
|
|
|
|
[1] Expected false to equal true.
|
|
|
|
|
[1] Chrome 51.0.2704: Executed 1 of 1 (1 FAILED) (0.005 secs / 0.005 secs)
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
It failed of course.
|
|
|
|
|
|
|
|
|
|
Restore the expectation from `false` back to `true`.
|
|
|
|
|
Both processes detect the change, re-run, and karma reports complete success.
|
|
|
|
|
|
|
|
|
|
.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.
|
2016-02-06 02:27:06 -05:00
|
|
|
|
|
2016-09-13 17:39:39 -04:00
|
|
|
|
If it says `FAILED`, scroll up to look for the error or, if that's too painful,
|
|
|
|
|
pipe the console output to a file and inspect with your favorite editor.
|
|
|
|
|
code-example(format="." language="json").
|
|
|
|
|
npm test > spec-output.txt
|
2016-02-06 02:27:06 -05:00
|
|
|
|
|
2016-09-13 17:39:39 -04:00
|
|
|
|
:marked
|
|
|
|
|
## Test debugging
|
|
|
|
|
|
|
|
|
|
Debug specs in the browser in the same way you debug an application.
|
|
|
|
|
|
|
|
|
|
- Reveal the karma browser window (hidden earlier).
|
|
|
|
|
- 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.
|
|
|
|
|
|
|
|
|
|
figure.image-display
|
|
|
|
|
img(src='/resources/images/devguide/testing/karma-1st-spec-debug.png' style="width:700px;" alt="Karma debugging")
|
2016-02-06 02:27:06 -05:00
|
|
|
|
|
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
|
|
|
|
|
.l-hr
|
2016-09-13 17:39:39 -04:00
|
|
|
|
a#atp-intro
|
|
|
|
|
:marked
|
|
|
|
|
# The Angular Testing Platform (ATP)
|
|
|
|
|
|
|
|
|
|
Many tests explore how applications classes interact with Angular and the DOM while under Angular's control.
|
|
|
|
|
|
|
|
|
|
Such tests are easy to write with the help of the _Angular Testing Platform_ (ATP)
|
|
|
|
|
which consists of the `TestBed` class and some helper functions.
|
|
|
|
|
|
|
|
|
|
Tests written with the _Angular Testing Platform_ are the main focus of this chapter.
|
|
|
|
|
But they are not the only tests you should write.
|
|
|
|
|
|
|
|
|
|
### Isolated unit tests
|
|
|
|
|
|
|
|
|
|
You can and should write [isolated unit tests](#testing-without-atp "Testing without the Angular Testing Platform")
|
|
|
|
|
for components, directives, pipes, and services.
|
|
|
|
|
Isolated unit tests examine an instance of a class all by itself without
|
|
|
|
|
any dependence on Angular or any injected values.
|
|
|
|
|
The tester creates a test instance of the class with new, supplying fake constructor parameters as needed, and
|
|
|
|
|
then probes the test instance API surface.
|
|
|
|
|
|
|
|
|
|
Isolated tests don't reveal how the class interacts with Angular.
|
|
|
|
|
In particular, they can't reveal how a component class interacts with its own template or with other components.
|
|
|
|
|
|
|
|
|
|
Those tests require the Angular Testing Platform.
|
|
|
|
|
|
|
|
|
|
### Testing with the _ Angular Testing Platform_
|
|
|
|
|
|
|
|
|
|
The _Angular Testing Platform_ consists of the `TestBed` class and some helper functions from `@angular/core/testing`.
|
|
|
|
|
.alert.is-important
|
|
|
|
|
:marked
|
|
|
|
|
The _TestBed_ is officially _experimental_ and thus subject to change.
|
|
|
|
|
Consult the [API reference](../api/core/testing/index/TestBed-class.html) for the latest status.
|
|
|
|
|
:marked
|
|
|
|
|
The `TestBed` creates an Angular test module — an `@NgModule` class —
|
|
|
|
|
that you configure to produce the module environment for the class you want to test.
|
|
|
|
|
You tell the `TestBed` to create an instance of the test component and probe that instance with tests.
|
|
|
|
|
|
|
|
|
|
That's the `TestBed` in a nutshell.
|
|
|
|
|
|
|
|
|
|
In practice, you work with the static methods of the `TestBed` class.
|
|
|
|
|
These static methods create and update a fresh hidden `TestBed` instance before each Jasmine `it`.
|
|
|
|
|
.l-sub-section
|
|
|
|
|
:marked
|
|
|
|
|
You can access that hidden instance anytime by calling `getTestBed()`;
|
2016-02-06 02:27:06 -05:00
|
|
|
|
:marked
|
2016-09-13 17:39:39 -04:00
|
|
|
|
This `TestBed` instance comes pre-configured with a baseline of default providers and declarables (components, directives, and pipes)
|
|
|
|
|
that almost everyone needs.
|
|
|
|
|
This chapter tests a browser application so the default includes the `CommonModule` declarables from `@angular/common`
|
|
|
|
|
and the `BrowserModule` providers (some of them mocked) from `@angular/platform-browser`.
|
|
|
|
|
|
|
|
|
|
You refine the default test module configuration with application and test specifics
|
|
|
|
|
so that it can produce an instance of the test component in the Angular environment suitable for your tests.
|
|
|
|
|
|
|
|
|
|
Start by calling `TestBed.configureTestingModule` with an object that looks like `@NgModule` metadata.
|
|
|
|
|
This object defines additional imports, declarations, providers and schemas.
|
|
|
|
|
|
|
|
|
|
After configuring the `TestBed`, tell it to create an instance of the test component and the test fixture
|
|
|
|
|
you'll need to inspect and control the component's immediate environment.
|
|
|
|
|
|
|
|
|
|
+makeExample('testing/ts/app/banner.component.spec.ts', 'simple-example-before-each', 'app/banner.component.spec.ts (simplified)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
Angular tests can interact with the HTML in the test DOM,
|
|
|
|
|
simulate user activity, tell Angular to perform specific task (such as change detection),
|
|
|
|
|
and see the effects of these actions both in the test component and in the test DOM.
|
|
|
|
|
+makeExample('testing/ts/app/banner.component.spec.ts', 'simple-example-it', 'app/banner.component.spec.ts (simplified)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
A comprehensive review of the _TestBed_ API appears [later in the chapter](#atp-api).
|
|
|
|
|
Let's dive right into Angular testing, starting with with the components of a sample application.
|
|
|
|
|
|
2016-02-06 02:27:06 -05:00
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
|
|
|
|
|
.l-hr
|
2016-09-13 17:39:39 -04:00
|
|
|
|
|
|
|
|
|
a#sample-app
|
|
|
|
|
:marked
|
|
|
|
|
# The sample application and its tests
|
|
|
|
|
|
|
|
|
|
This chapter tests a cut-down version of the _Tour of Heroes_ [tutorial app](../tutorial).
|
|
|
|
|
|
|
|
|
|
The following live example shows how it works and provides the complete source code.
|
|
|
|
|
<live-example embedded img="devguide/testing/app-plunker.png"></live-example>
|
|
|
|
|
<br><br>
|
|
|
|
|
:marked
|
|
|
|
|
The following live example runs all the tests of this application
|
|
|
|
|
inside the browser, using the Jasmine Test Runner instead of karma.
|
|
|
|
|
|
|
|
|
|
It includes the tests discussed in this chapter and additional tests for you to explore.
|
|
|
|
|
This live example contains both application and test code.
|
|
|
|
|
It is large and can take several minutes to start. Please be patient.
|
|
|
|
|
<live-example plnkr="app-specs" embedded img="devguide/testing/app-specs-plunker.png"></live-example>
|
|
|
|
|
|
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
.l-hr
|
|
|
|
|
|
|
|
|
|
a#simple-component-test
|
|
|
|
|
:marked
|
|
|
|
|
# Test a component
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
The top of the screen displays application title, presented by the `BannerComponent` in `app/banner.component.ts`.
|
|
|
|
|
+makeExample('testing/ts/app/banner.component.ts', '', 'app/banner.component.ts')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
`BannerComponent` has an inline template and an interpolation binding, about as simple as it gets.
|
|
|
|
|
Probably too simple to be worth testing in real life but perfect for a first encounter with the `TestBed`.
|
|
|
|
|
|
|
|
|
|
The corresponding `app/banner-component.spec.ts` sits in the same folder as the component,
|
|
|
|
|
for reasons explained [here](#q-spec-file-location);
|
|
|
|
|
|
|
|
|
|
Start with ES6 import statements to get access to symbols referenced in the spec.
|
|
|
|
|
+makeExample('testing/ts/app/banner.component.spec.ts', 'imports', 'app/banner.component.spec.ts (imports)')(format='.')
|
2016-02-06 02:27:06 -05:00
|
|
|
|
:marked
|
2016-09-13 17:39:39 -04:00
|
|
|
|
Here's the setup for the tests followed by observations about the `beforeEach`:
|
|
|
|
|
+makeExample('testing/ts/app/banner.component.spec.ts', 'setup', 'app/banner.component.spec.ts (imports)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
`TestBed.configureTestingModule` takes an `@NgModule`-like metadata object.
|
|
|
|
|
This one simply declares the component to test, `BannerComponent`.
|
|
|
|
|
|
|
|
|
|
It lacks `imports` because (a) it extends the default test module configuration which
|
|
|
|
|
already has what `BannerComponent` needs
|
|
|
|
|
and (b) `BannerComponent` doesn't interact with any other components.
|
|
|
|
|
|
|
|
|
|
The configuration could have imported `AppModule` (which declares `BannerComponent`).
|
|
|
|
|
But that would lead to tons more configuration in order to support the other components within `AppModule`
|
|
|
|
|
that have nothing to do with `BannerComponent`.
|
|
|
|
|
|
|
|
|
|
`TestBed.createComponent` creates an instance of `BannerComponent` to test.
|
|
|
|
|
The 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.
|
|
|
|
|
|
|
|
|
|
Query the `DebugElement` by CSS selector for the `<h1>` sub-element that holds the actual title.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### _createComponent_ closes configuration
|
|
|
|
|
`TestBed.createComponent` closes the current `TestBed` instance to further configuration.
|
|
|
|
|
You cannot call any more `TestBed` configuration methods, not `configureTestModule`
|
|
|
|
|
nor any of the `override...` methods. The `TestBed` throws an error if you try.
|
|
|
|
|
|
|
|
|
|
.alert.is-important
|
|
|
|
|
:marked
|
|
|
|
|
Do not configure the `TestBed` after calling `createComponent`.
|
|
|
|
|
:marked
|
|
|
|
|
### The tests
|
|
|
|
|
Jasmine runs this `beforeEach` before each test of which there are two
|
|
|
|
|
+makeExample('testing/ts/app/banner.component.spec.ts', 'tests', 'app/banner.component.spec.ts (tests)')(format='.')
|
|
|
|
|
:markdown
|
|
|
|
|
These tests ask the `DebugElement` for the native HTML element to satisfy their expectations.
|
|
|
|
|
|
|
|
|
|
a#fixture-detect-changes
|
|
|
|
|
:marked
|
|
|
|
|
### _detectChanges_: Angular change detection under test
|
|
|
|
|
|
|
|
|
|
Each test tells Angular when to perform change detection by calling `fixture.detectChanges()`.
|
|
|
|
|
The first test does so immediately, triggering data binding and propagation of the `title` property
|
|
|
|
|
to the DOM element.
|
|
|
|
|
|
|
|
|
|
The second test changes the component's `title` property _and only then_ calls `fixture.detectChanges()`;
|
|
|
|
|
the new value appears in the DOM element.
|
|
|
|
|
|
|
|
|
|
In production, change detection kicks in automatically
|
|
|
|
|
when Angular creates a component or the user enters a keystroke or
|
|
|
|
|
an asynchronous activity (e.g., AJAX) completes.
|
|
|
|
|
|
|
|
|
|
The `TestBed.createComponent` does _not_ trigger change detection.
|
|
|
|
|
The fixture does not automatically push the component's `title` property value into the data bound element,
|
|
|
|
|
a fact demonstrated in the following test:
|
|
|
|
|
+makeExample('testing/ts/app/banner.component.spec.ts', 'test-w-o-detect-changes', 'app/banner.component.spec.ts (no detectChanges)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
This behavior (or lack of it) is intentional.
|
|
|
|
|
It gives the tester an opportunity to investigate the state of
|
|
|
|
|
the component _before Angular initiates data binding or calls lifecycle hooks_.
|
|
|
|
|
|
|
|
|
|
a#automatic-change-detection
|
|
|
|
|
:marked
|
|
|
|
|
### Automatic change detection
|
|
|
|
|
Some testers prefer that the Angular test environment run change detection automatically.
|
|
|
|
|
That's possible by configuring the `TestBed` with the _AutoDetect_ provider:
|
|
|
|
|
+makeExample('testing/ts/app/banner.component.spec.ts', 'auto-detect', 'app/banner.component.spec.ts (AutoDetect)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
Here are three tests that illustrate how _auto-detect_ works.
|
|
|
|
|
+makeExample('testing/ts/app/banner.component.spec.ts', 'auto-detect-tests', 'app/banner.component.spec.ts (AutoDetect Tests)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
The first test shows the benefit of automatic change detection.
|
|
|
|
|
|
|
|
|
|
The second and third test remind us that Angular does _not_ know about changes to component property
|
|
|
|
|
values unless Angular itself (or some asynchronous process) makes the change.
|
|
|
|
|
This is as true in production as it is in test.
|
|
|
|
|
|
|
|
|
|
In production, external forces rarely change component properties like this,
|
|
|
|
|
whereas these kinds of probing changes are typical in unit tests.
|
|
|
|
|
The tester will have to call `fixture.detectChanges()` quite often
|
|
|
|
|
despite having opted into auto detect.
|
|
|
|
|
|
|
|
|
|
.alert.is-helpful
|
|
|
|
|
:marked
|
|
|
|
|
Rather than wonder when the test fixture will or won't perform change detection,
|
|
|
|
|
the samples in this chapter _always call_ `detectChanges()` _explicitly_.
|
|
|
|
|
|
2016-02-06 02:27:06 -05:00
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
|
|
|
|
|
.l-hr
|
2016-09-13 17:39:39 -04:00
|
|
|
|
|
|
|
|
|
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/app/welcome.component.ts', '', 'app/welcome.component.ts')(format='.')
|
2016-02-06 02:27:06 -05:00
|
|
|
|
:marked
|
2016-09-13 17:39:39 -04:00
|
|
|
|
The `WelcomeComponent` has decision logic that interacts with the service;
|
|
|
|
|
such logic makes this component worth testing.
|
|
|
|
|
Here's the test module configuration for the spec file, `app/welcome.component.spec.ts`:
|
|
|
|
|
+makeExample('testing/ts/app/welcome.component.spec.ts', 'config-test-module', 'app/welcome.component.spec.ts')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
This time, in addition to declaring the component under test,
|
|
|
|
|
the configurations sets the `providers` list with the dependent `UserService`.
|
|
|
|
|
|
|
|
|
|
This example configures the test module with a _fake_ `UserService`.
|
|
|
|
|
|
|
|
|
|
## Provide service fakes
|
|
|
|
|
|
|
|
|
|
A component under test doesn't have to be injected with real services.
|
|
|
|
|
In fact, it is usually better if they are fakes.
|
|
|
|
|
The purpose of the spec is to test the component, not the service,
|
|
|
|
|
and real services can be trouble.
|
|
|
|
|
|
|
|
|
|
Injecting the real `UserService` could be a nightmare.
|
|
|
|
|
The real service might try to ask the user for login credentials and
|
|
|
|
|
try to reach an authentication server.
|
|
|
|
|
These behaviors could be hard to intercept.
|
|
|
|
|
It is far easier to create and register a fake `UserService`.
|
|
|
|
|
|
|
|
|
|
There are many ways to fake a service.
|
|
|
|
|
This test suit supplies a minimal `UserService` that satisfies the needs of the `WelcomeComponent`
|
|
|
|
|
and its tests:
|
|
|
|
|
+makeExample('testing/ts/app/welcome.component.spec.ts', 'fake-userservice')(format='.')
|
|
|
|
|
|
|
|
|
|
a#injected-service-reference
|
|
|
|
|
:marked
|
|
|
|
|
## Referencing injected services
|
|
|
|
|
The tests need access to the injected (fake) `UserService`.
|
|
|
|
|
|
|
|
|
|
You cannot reference the `fakeUserService` object provided to the test module.
|
|
|
|
|
**It does not work!**
|
|
|
|
|
Surprisingly, the instance actually injected into the component is _not the same_
|
|
|
|
|
as the provided `fakeUserService` object.
|
|
|
|
|
|
|
|
|
|
.alert.is-important
|
|
|
|
|
:marked
|
|
|
|
|
Always use an injector to get a reference to an injected service.
|
|
|
|
|
:marked
|
|
|
|
|
Where do you get the injector?
|
|
|
|
|
Angular has an hierarchical injection system.
|
|
|
|
|
In a test there can be injectors at multiple levels.
|
|
|
|
|
The current `TestBed` injector creates a top-level injector.
|
|
|
|
|
The `WelcomeComponent` injector is a child of that injector created specifically for the component.
|
|
|
|
|
|
|
|
|
|
You can get a `UserService` from the current `TestBed` injector by calling `TestBed.get`.
|
|
|
|
|
+makeExample('testing/ts/app/welcome.component.spec.ts', 'inject-from-testbed', 'TestBed injector')(format='.')
|
|
|
|
|
.l-sub-section
|
|
|
|
|
:marked
|
|
|
|
|
The [inject](#inject) function is another way to inject one or more services into a test.
|
|
|
|
|
:marked
|
|
|
|
|
That happens to work for testing the `WelcomeComponent` because the `UserService` instance from the `TestBed`
|
|
|
|
|
is the same as the `UserService` instance injected into the component.
|
|
|
|
|
|
|
|
|
|
That won't always be the case.
|
|
|
|
|
Be absolutely sure to reference the service instance that the component is _actually receiving_,
|
|
|
|
|
Call `get` on the component's injector which is `fixture.debugElement.injector`:
|
|
|
|
|
+makeExample('testing/ts/app/welcome.component.spec.ts', 'injected-service', 'Component\'s injector')(format='.')
|
|
|
|
|
.alert.is-important
|
|
|
|
|
:marked
|
|
|
|
|
Use the component's own injector to get the component's injected service.
|
|
|
|
|
a#welcome-spec-setup
|
|
|
|
|
:marked
|
|
|
|
|
Here's the complete, preferred `beforeEach`:
|
|
|
|
|
+makeExample('testing/ts/app/welcome.component.spec.ts', 'setup', 'app/welcome.component.spec.ts')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
And here are some tests:
|
|
|
|
|
+makeExample('testing/ts/app/welcome.component.spec.ts', 'tests', 'app/welcome.component.spec.ts')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
The first is a sanity test; it confirms that the fake `UserService` is working.
|
|
|
|
|
The remaining tests confirm the logic of the component when the service returns different values.
|
|
|
|
|
The second test validates the effect of changing the user name.
|
|
|
|
|
The third test checks that the component displays the proper message when there is no logged-in user.
|
|
|
|
|
|
2016-02-06 02:27:06 -05:00
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
|
|
|
|
|
.l-hr
|
2016-09-13 17:39:39 -04:00
|
|
|
|
|
|
|
|
|
a#component-with-async-service
|
|
|
|
|
:marked
|
|
|
|
|
# Test a component with an async service
|
|
|
|
|
Many services return values asynchronously.
|
|
|
|
|
Most data services make an HTTP request to a remote server and the response is necessarily asynchronous.
|
|
|
|
|
|
|
|
|
|
The "About" view in this sample displays Mark Twain quotes.
|
|
|
|
|
The `TwainComponent` handles the display, delegating the server request to the `TwainService`.
|
|
|
|
|
Both are in the `app/shared` folder because the author intends to display Twain quotes on other pages someday.
|
|
|
|
|
Here is the `TwainComponent`.
|
|
|
|
|
+makeExample('testing/ts/app/shared/twain.component.ts', 'component', '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.
|
|
|
|
|
|
|
|
|
|
In general, tests should not make calls to remote servers.
|
|
|
|
|
They should fake such calls. The setup in this `app/shared/twain.component.spec.ts` shows one way to do that:
|
|
|
|
|
+makeExample('testing/ts/app/shared/twain.component.spec.ts', 'setup', 'app/shared/twain.component.spec.ts (setup)')(format='.')
|
|
|
|
|
|
|
|
|
|
a#service-spy
|
2016-02-06 02:27:06 -05:00
|
|
|
|
:marked
|
2016-09-13 17:39:39 -04:00
|
|
|
|
### Spying on the real service
|
|
|
|
|
|
|
|
|
|
This setup is similar to the [`welcome.component.spec` setup](#welcome-spec-setup).
|
|
|
|
|
But instead of creating a fake service object, it injects the _real_ service (see the test module `providers`) and
|
|
|
|
|
replaces the critical `getQuote` method with a Jasmine spy.
|
|
|
|
|
+makeExample('testing/ts/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.
|
|
|
|
|
|
|
|
|
|
.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. Don't be afraid to change your mind.
|
|
|
|
|
:marked
|
|
|
|
|
Here are the tests with commentary to follow:
|
|
|
|
|
+makeExample('testing/ts/app/shared/twain.component.spec.ts', 'tests', 'app/shared/twain.component.spec.ts (tests)')
|
|
|
|
|
:marked
|
|
|
|
|
### Synchronous tests
|
|
|
|
|
The first two tests are synchronous.
|
|
|
|
|
Neither test can prove that a value from the service will be displayed.
|
|
|
|
|
|
|
|
|
|
Thanks to the spy, the second test verifies that `getQuote` is called.
|
|
|
|
|
But 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, a least one "tick", before the
|
|
|
|
|
value becomes available. By that time, the test runner has moved on to the next test in the suite.
|
|
|
|
|
|
|
|
|
|
The test must become an "async test" ... like the third test
|
|
|
|
|
|
|
|
|
|
a#async-fn-in-it
|
|
|
|
|
:marked
|
|
|
|
|
## The _async_ function in _it_
|
|
|
|
|
|
|
|
|
|
Notice the `async` in the third test.
|
|
|
|
|
+makeExample('testing/ts/app/shared/twain.component.spec.ts', 'async-test', 'app/shared/twain.component.spec.ts (async test)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
The `async` function is part of the _Angular TestBed_ feature set.
|
|
|
|
|
It _takes_ a parameterless function and _returns_ a parameterless function
|
|
|
|
|
which becomes the argument to the Jasmine `it` call.
|
|
|
|
|
|
|
|
|
|
The body of the `async` argument looks much like the body of a normal `it` argument.
|
|
|
|
|
There is nothing obviously asynchronous about it. For example, it doesn't return a promise.
|
|
|
|
|
|
|
|
|
|
The `async` function arranges for the tester's code to run in a special _async test zone_
|
|
|
|
|
that almost hides the mechanics of asynchronous execution.
|
|
|
|
|
|
|
|
|
|
Almost but not completely.
|
|
|
|
|
|
|
|
|
|
a#when-stable
|
|
|
|
|
:marked
|
|
|
|
|
## _whenStable_
|
|
|
|
|
The test must wait for the `getQuote` promise to resolve.
|
|
|
|
|
|
|
|
|
|
The `getQuote` promise promise resolves in the next turn of the JavaScript engine, thanks to the spy.
|
|
|
|
|
But a different test implementation of `getQuote` could take longer.
|
|
|
|
|
An integration test might call the _real_ `getQuote`, resulting in an XHR request
|
|
|
|
|
that took many seconds to respond.
|
|
|
|
|
|
|
|
|
|
This test has no direct access to the promise returned by the call to `testService.getQuote`
|
|
|
|
|
which is private and inaccessible inside `TwainComponent`.
|
|
|
|
|
|
|
|
|
|
Fortunately, the `getQuote` promise is accessible to the _async test zone_
|
|
|
|
|
which intercepts all promises issued within the _async_ method call.
|
|
|
|
|
|
|
|
|
|
The `ComponentFixture.whenStable` method returns its own promise which resolves when the `getQuote` promise completes.
|
|
|
|
|
In fact, the _whenStable_ promise resolves when _all pending asynchronous activities_ complete ... the definition of "stable".
|
|
|
|
|
|
|
|
|
|
Then the testing continues.
|
|
|
|
|
The test kicks off another round of change detection (`fixture.detechChanges`) 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 fourth test verifies the same component behavior in a different way.
|
|
|
|
|
+makeExample('testing/ts/app/shared/twain.component.spec.ts', 'fake-async-test', 'app/shared/twain.component.spec.ts (fakeAsync test)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
Notice that `fakeAsync` replaces `async` as the `it` argument.
|
|
|
|
|
The `fakeAsync` function is also part of the _Angular TestBed_ feature set.
|
|
|
|
|
Like `async`, it too _takes_ a parameterless function and _returns_ a parameterless function
|
|
|
|
|
which becomes the argument to the Jasmine `it` call.
|
|
|
|
|
|
|
|
|
|
The `async` function arranges for the tester's code to run in a special _fakeAsync test zone_.
|
|
|
|
|
|
|
|
|
|
The key advantage of `fakeAsync` is that the test body looks entirely synchronous.
|
|
|
|
|
There are no promises at all.
|
|
|
|
|
No `then(...)` chains to disrupt the visible flow of control.
|
|
|
|
|
|
|
|
|
|
.l-sub-section
|
|
|
|
|
:marked
|
|
|
|
|
There are limitations. For example, you cannot make an XHR call from within a `fakeAsync`.
|
|
|
|
|
:marked
|
|
|
|
|
|
|
|
|
|
a#tick
|
|
|
|
|
a#tick-first-look
|
|
|
|
|
:marked
|
|
|
|
|
## The _tick_ function
|
|
|
|
|
Compare the third and fourth tests. Notice that `fixture.whenStable` is gone, replaced by `tick()`.
|
|
|
|
|
|
|
|
|
|
The `tick` function is a part of the _Angular TestBed_ feature set and a companion to `fakeAsync`.
|
|
|
|
|
It can only be called within a `fakeAsync` body.
|
|
|
|
|
|
|
|
|
|
Calling `tick()` simulates the passage of time until all pending asynchronous activities complete,
|
|
|
|
|
including the resolution of the `getQuote` promise in this test case.
|
|
|
|
|
|
|
|
|
|
It returns nothing. There is no promise to wait for.
|
|
|
|
|
Proceed with the same test code as formerly appeared within the `whenStable.then()` callback.
|
|
|
|
|
|
|
|
|
|
Even this simple example is easier to read than the third test.
|
|
|
|
|
To more fully appreciate the improvement, imagine a succession of asynchronous operations,
|
|
|
|
|
chained in a long sequence of promise callbacks.
|
|
|
|
|
|
|
|
|
|
a#jasmine-done
|
|
|
|
|
:marked
|
|
|
|
|
## _jasmine.done_
|
|
|
|
|
|
|
|
|
|
While `fakeAsync` and even `async` function greatly simplify Angular asynchronous testing,
|
|
|
|
|
you can still fallback to the traditional Jasmine asynchronous testing technique.
|
|
|
|
|
|
|
|
|
|
You can still pass `it` a function that takes a
|
|
|
|
|
[`done` callback](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/app/shared/twain.component.spec.ts', 'done-test', '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.
|
|
|
|
|
|
2016-02-06 02:27:06 -05:00
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
|
2016-09-13 17:39:39 -04:00
|
|
|
|
.l-hr
|
|
|
|
|
|
|
|
|
|
a#component-with-external-template
|
|
|
|
|
:marked
|
|
|
|
|
# Test a component with an external template
|
|
|
|
|
The `TestBed.createComponent` is a synchronous method.
|
|
|
|
|
It assumes that everything it could need is already in memory.
|
|
|
|
|
|
|
|
|
|
That has been true so far.
|
|
|
|
|
Each tested component's `@Component` metadata has a `template` property specifying an _inline templates_.
|
|
|
|
|
Neither component had a `styleUrls` property.
|
|
|
|
|
Everything necessary to compile them was in memory at test runtime.
|
|
|
|
|
|
|
|
|
|
The `DashboardHeroComponent` is different.
|
|
|
|
|
It has an external template and external css file, specified in `templateUrl` and `styleUrls` properties.
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard-hero.component.ts', 'component', 'app/dashboard/dashboard-hero.component.ts (component)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
The compiler must read these files from a file system before it can create a component instance.
|
|
|
|
|
|
|
|
|
|
The `TestBed.compileComponents` method asynchronously compiles all the components configured in its
|
|
|
|
|
current test module. After it completes, external templates and css files, have been "inlined"
|
|
|
|
|
and `TestBed.createComponent` can do its job synchronously.
|
|
|
|
|
.l-sub-section
|
|
|
|
|
:marked
|
|
|
|
|
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
|
|
|
|
|
The `app/dashboard/dashboard-hero.component.spec.ts` demonstrates the pre-compilation process:
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'compile-components', 'app/dashboard/dashboard-hero.component.spec.ts (compileComponents)')(format='.')
|
|
|
|
|
|
|
|
|
|
a#async-fn-in-before-each
|
|
|
|
|
:marked
|
|
|
|
|
## The _async_ function in _beforeEach_
|
|
|
|
|
|
|
|
|
|
Notice the `async` call in the `beforeEach`.
|
|
|
|
|
|
|
|
|
|
The `async` function is part of the _Angular TestBed_ feature set.
|
|
|
|
|
It _takes_ a parameterless function and _returns_ a parameterless function
|
|
|
|
|
which becomes the argument to the Jasmine `beforeEach` call.
|
|
|
|
|
|
|
|
|
|
The body of the `async` argument looks much like the body of a normal `beforEach` argument.
|
|
|
|
|
There is nothing obviously asynchronous about it. For example, it doesn't return a promise.
|
|
|
|
|
|
|
|
|
|
The `async` function arranges for the tester's code to run in a special _async test zone_
|
|
|
|
|
that hides the mechanics of asynchronous execution.
|
|
|
|
|
|
|
|
|
|
a#compile-components
|
|
|
|
|
:marked
|
|
|
|
|
## _compileComponents_
|
|
|
|
|
In this example, `Testbed.compileComponents` compiles one component, the `DashboardComponent`.
|
|
|
|
|
It's the only declared component in this test module.
|
|
|
|
|
|
|
|
|
|
Tests later in this chapter have more declared components and some of them import application
|
|
|
|
|
modules that declare yet more components.
|
|
|
|
|
Some or all of these components could have external templates and css files.
|
|
|
|
|
`TestBed.compileComponents` compiles them all asynchonously at one time.
|
|
|
|
|
|
|
|
|
|
The `compileComponents` method returns a promise so you can perform additional tasks _after_ it finishes.
|
|
|
|
|
|
|
|
|
|
### _compileComponents_ closes configuration
|
|
|
|
|
After `compileComponents` runs, the current `TestBed` instance is closed to further configuration.
|
|
|
|
|
You cannot call any more `TestBed` configuration methods, not `configureTestModule`
|
|
|
|
|
nor any of the `override...` methods. The `TestBed` throws an error if you try.
|
|
|
|
|
|
|
|
|
|
.alert.is-important
|
|
|
|
|
:marked
|
|
|
|
|
Do not configure the `TestBed` after calling `compileComponents`.
|
|
|
|
|
Make `compileComponents` the last step
|
|
|
|
|
before calling `TestBed.createInstance` to instantiate the test component.
|
|
|
|
|
:marked
|
|
|
|
|
The `DashboardHeroComponent` spec follows the asynchonous `beforeEach` with a
|
|
|
|
|
_synchronous_ `beforeEach` that completes the setup steps and runs tests ... as described in the next section.
|
|
|
|
|
|
|
|
|
|
.l-hr
|
|
|
|
|
|
|
|
|
|
a#component-with-inputs-outputs
|
|
|
|
|
: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
|
|
|
|
|
listen to events raised by the output property.
|
|
|
|
|
|
|
|
|
|
The testing goal is to verify that such bindings work as expected.
|
|
|
|
|
The tests should set input values and listen for output events.
|
|
|
|
|
|
|
|
|
|
The `DashboardHeroComponent` is tiny example of a component in this role.
|
|
|
|
|
It displays an individual heroe provided by the `DashboardComponent`.
|
|
|
|
|
Clicking that hero tells the the `DashboardComponent` that the user has selected the hero.
|
|
|
|
|
|
|
|
|
|
The `DashboardHeroComponent` is embedded in the `DashboardComponent` template like this:
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard.component.html', 'dashboard-hero', '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.
|
|
|
|
|
|
|
|
|
|
Here's the component's definition again:
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard-hero.component.ts', 'component', '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`
|
|
|
|
|
|
|
|
|
|
A quick look at the `DashboardComponent` constructor discourages the first approach:
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard.component.ts', 'ctor', 'app/dashboard/dashboard.component.ts (constructor)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
The `DashboardComponent` depends upon the Angular router and the `HeroService`.
|
|
|
|
|
You'd probably have to fake them both and that's a lot of work. The router is particularly challenging (see below).
|
|
|
|
|
|
|
|
|
|
The immediate goal is to test the `DashboardHeroComponent`, not the `DashboardComponent`, and there's no need
|
|
|
|
|
to work hard unnecessarily. Let's try the second and third options.
|
|
|
|
|
|
|
|
|
|
## Test _DashboardHeroComponent_ stand-alone
|
|
|
|
|
|
|
|
|
|
Here's the spec file setup.
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'setup', '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
|
|
|
|
|
proceeds _synchronously_ in a _second_ `beforeEach`, using the basic techniques described [earlier](#simple-component-test).
|
|
|
|
|
|
|
|
|
|
Note how the setup code assigns a test hero (`expectedHero`) to the component's `hero` property, emulating
|
|
|
|
|
the way the `DashboardComponent` would set it via the property binding in its repeater.
|
|
|
|
|
|
|
|
|
|
The first test follows:
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'name-test', '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:
|
|
|
|
|
+makeExample('testing/ts/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.
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
The second test verifies click behavior. Clicking the hero should rais a `selected` event that the
|
|
|
|
|
host component (`DashboardComponent` presumably) can hear:
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'click-test', 'app/dashboard/dashboard-hero.component.spec.ts (click test)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
The component exposes an `EventEmitter` property. The test subscribes to it just as the host component would do.
|
|
|
|
|
|
|
|
|
|
The Angular `DebugElement.triggerEventHandler` lets the test raise _any data-bound event_.
|
|
|
|
|
In this example, the component's template binds to the hero `<div>`.
|
|
|
|
|
|
|
|
|
|
The test has a reference to that `<div>` in `heroEl` so triggering the `heroEl` click event should cause Angular
|
|
|
|
|
to call `DashboardHeroComponent.click`.
|
|
|
|
|
|
|
|
|
|
If the component behaves as expected, its `selected` property should emit the `hero` object,
|
|
|
|
|
the test detects that emission through its subscription, and the test will pass.
|
|
|
|
|
|
|
|
|
|
.l-hr
|
|
|
|
|
|
|
|
|
|
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?
|
|
|
|
|
|
|
|
|
|
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/app/dashboard/dashboard-hero.component.spec.ts', 'test-host', '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 test host sets the component's `hero` input property with its test hero.
|
|
|
|
|
It binds the component's `selected` event with its `onSelected` handler that records the emitted hero
|
|
|
|
|
in its `selectedHero` property. Later the tests check that property to verify that the
|
|
|
|
|
`DashboardHeroComponent.selected` event really did emit the right hero.
|
|
|
|
|
|
|
|
|
|
The setup for the test-host tests is similar to the setup for the stand-alone tests:
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'test-host-setup', 'app/dashboard/dashboard-hero.component.spec.ts (test host setup)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
This test module configuration shows two important differences:
|
|
|
|
|
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
|
|
|
|
|
albeit at greater depth in the element tree than before.
|
|
|
|
|
|
|
|
|
|
The tests themselves are almost identical to the stand-alone version
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard-hero.component.spec.ts', 'test-host-tests', '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.
|
|
|
|
|
|
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
|
|
|
|
|
.l-hr
|
|
|
|
|
|
|
|
|
|
a#routed-component
|
|
|
|
|
:marked
|
|
|
|
|
# Test a routed component
|
|
|
|
|
|
|
|
|
|
Testing the actual `DashboardComponent` seemed daunting because it injects the `Router`.
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard.component.ts', 'ctor', 'app/dashboard/dashboard.component.ts (constructor)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
It also injects the `HeroService` but faking that is a [familiar story](#component-with-async-servic).
|
|
|
|
|
The `Router` has a complicated API and is entwined with other services and application pre-conditions.
|
|
|
|
|
|
|
|
|
|
Fortunately, the `DashboardComponent` isn't doing much with the `Router`
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard.component.ts', 'goto-detail', 'app/dashboard/dashboard.component.ts (goToDetail)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
This is often the case.
|
|
|
|
|
As a rule you test the component, not the router,
|
|
|
|
|
and care only if the component navigates with the right address under the given conditions.
|
|
|
|
|
Faking the router is an easy option. This should do the trick:
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard.component.spec.ts', 'fake-router', 'app/dashboard/dashboard.component.spec.ts (fakeRouter)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
Now we setup the test module with the `fakeRouter` and a fake `HeroService` and
|
|
|
|
|
create a test instance of the `DashbaordComponent` for subsequent testing.
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard.component.spec.ts', 'compile-and-create-body', '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/app/dashboard/dashboard.component.spec.ts', 'navigate-test', 'app/dashboard/dashboard.component.spec.ts (navigate test)')(format='.')
|
|
|
|
|
|
|
|
|
|
a#inject
|
|
|
|
|
:marked
|
|
|
|
|
## The _inject_ function
|
|
|
|
|
|
|
|
|
|
Notice the `inject` function in the second `it` argument.
|
|
|
|
|
+makeExample('testing/ts/app/dashboard/dashboard.component.spec.ts', 'inject')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
The `inject` function is part of the _Angular TestBed_ feature set.
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
.callout.is-important
|
|
|
|
|
header inject uses the TestBed Injector
|
|
|
|
|
:marked
|
|
|
|
|
The `inject` function uses the current `TestBed` injector and can only return services provided at that level.
|
|
|
|
|
It does not return services from component providers.
|
|
|
|
|
|
|
|
|
|
: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.
|
|
|
|
|
|
|
|
|
|
If you need a service provided by the component's _own_ injector, call `fixture.debugElement.injector.get` instead:
|
|
|
|
|
+makeExample('testing/ts/app/welcome.component.spec.ts', 'injected-service', 'Component\'s injector')(format='.')
|
|
|
|
|
.alert.is-important
|
|
|
|
|
:marked
|
|
|
|
|
Use the component's own injector to get the service actually injected into the component.
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
The `inject` function closes the current `TestBed` instance to further configuration.
|
|
|
|
|
You cannot call any more `TestBed` configuration methods, not `configureTestModule`
|
|
|
|
|
nor any of the `override...` methods. The `TestBed` throws an error if you try.
|
|
|
|
|
|
2016-02-06 02:27:06 -05:00
|
|
|
|
.alert.is-important
|
|
|
|
|
:marked
|
2016-09-13 17:39:39 -04:00
|
|
|
|
Do not configure the `TestBed` after calling `inject`.
|
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
|
|
|
|
|
.l-hr
|
|
|
|
|
|
|
|
|
|
a#isolated-tests
|
|
|
|
|
a#testing-without-atp
|
|
|
|
|
:marked
|
|
|
|
|
# Testing without the Angular Testing Platform
|
|
|
|
|
|
|
|
|
|
Testing applications with the help of the Angular Testing Platform (ATP) is the main focus of this chapter.
|
|
|
|
|
|
|
|
|
|
However, it's often more productive to explore the inner logic of application classes
|
|
|
|
|
with _isolated_ unit tests that don't use the ATP.
|
|
|
|
|
Such tests are often smaller, easier to read,
|
|
|
|
|
and easier to 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 do
|
|
|
|
|
* exhibit standard, Angular-agnostic testing techniques
|
|
|
|
|
* create instances directly with `new`
|
|
|
|
|
* use stubs, spys, and mocks to fake dependencies.
|
|
|
|
|
|
|
|
|
|
.callout.is-important
|
|
|
|
|
header Write both kinds of tests
|
|
|
|
|
:marked
|
|
|
|
|
Good developers write both kinds of tests for the same application part, often in the same spec file.
|
|
|
|
|
Write simple _isolated_ unit tests to validate the part in isolation.
|
|
|
|
|
Write _Angular_ tests to validate the part as it interacts with Angular,
|
|
|
|
|
updates the DOM, and collaborates with the rest of the application.
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
## Services
|
|
|
|
|
Services are good candidates for vanilla unit testing.
|
|
|
|
|
Here are some synchronous and asynchronous unit tests of the `FancyService`
|
|
|
|
|
written without assistance from Angular Testing Platform.
|
|
|
|
|
|
|
|
|
|
+makeExample('testing/ts/app/bag/bag.no-testbed.spec.ts', 'FancyService', 'app/bag/bag.no-testbed.spec.ts')
|
|
|
|
|
:marked
|
|
|
|
|
A rough line count suggests that these tests are about 25% smaller than equivalent ATP 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/app/bag/bag.no-testbed.spec.ts, testing/ts/app/bag/bag.spec.ts`,
|
|
|
|
|
'getTimeoutValue, getTimeoutValue',
|
|
|
|
|
`app/bag/bag.no-testbed.spec.ts, app/bag/bag.spec.ts (with ATP)`)
|
|
|
|
|
:marked
|
|
|
|
|
They have about the same line-count.
|
|
|
|
|
The ATP version has more moving parts, including a couple of helper functions (`async` and `inject`).
|
|
|
|
|
Both work and it's not much of an issue if you're using the Angular Testing Platform nearby for other reasons.
|
|
|
|
|
On the other hand, why burden simple service tests with ATP complexity?
|
|
|
|
|
|
|
|
|
|
Pick the approach that suits you.
|
|
|
|
|
|
|
|
|
|
### Services with dependencies
|
|
|
|
|
|
|
|
|
|
Services often depend on other services that Angular injects into the constructor.
|
|
|
|
|
You can test these services _without_ the testbed.
|
|
|
|
|
In many cases, it's easier to create and _inject_ dependencies by hand.
|
|
|
|
|
|
|
|
|
|
The `DependentService` is a simple example
|
|
|
|
|
+makeExample('testing/ts/app/bag/bag.ts', 'DependentService', 'app/bag/bag.ts')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
It delegates it's only method, `getValue`, to the injected `FancyService`.
|
|
|
|
|
|
|
|
|
|
Here are several ways to test it.
|
|
|
|
|
+makeExample('testing/ts/app/bag/bag.no-testbed.spec.ts', 'DependentService', '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 fake value, or stub the pertinent service method
|
|
|
|
|
with a substitute method that is easy to control.
|
|
|
|
|
|
|
|
|
|
These _isolated_ unit testing techniques are great for exploring the inner logic of a service or its
|
|
|
|
|
simple integration with a component class.
|
|
|
|
|
Use the Angular Testing Platform when writing tests that validate how a service interacts with components
|
|
|
|
|
_within the Angular runtime environment_.
|
|
|
|
|
|
|
|
|
|
## Pipes
|
|
|
|
|
Pipes are easy to test without the Angular Testing Platform (ATP).
|
|
|
|
|
|
|
|
|
|
A pipe class has one method, `transform`, that turns an input to an output.
|
|
|
|
|
The `transform` implementation rarely interacts with the DOM.
|
|
|
|
|
Most pipes have no dependence on Angular other than the `@Pipe`
|
|
|
|
|
metadata and an interface.
|
|
|
|
|
|
|
|
|
|
Consider a `TitleCasePipe` that capitalizes the first letter of each word.
|
|
|
|
|
Here's a naive implementation implemented with a regular expression.
|
|
|
|
|
+makeExample('testing/ts/app/shared/title-case.pipe.ts', '', '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/app/shared/title-case.pipe.spec.ts', 'excerpt', 'app/shared/title-case.pipe.spec.ts')
|
|
|
|
|
:marked
|
|
|
|
|
## Write ATP tests too
|
|
|
|
|
These are tests of the pipe _in isolation_.
|
|
|
|
|
They can't tell if the `TitleCasePipe` is working properly
|
|
|
|
|
as applied in the application components.
|
|
|
|
|
|
|
|
|
|
Consider adding ATP component tests such as this one.
|
|
|
|
|
+makeExample('testing/ts/app/hero/hero-detail.component.spec.ts', 'title-case-pipe', 'app/hero/hero-detail.component.spec.ts (pipe test)')
|
|
|
|
|
|
|
|
|
|
a#isolated-component-tests
|
|
|
|
|
:marked
|
|
|
|
|
## Components
|
|
|
|
|
|
|
|
|
|
Component tests typically examine how a component class interacts with its own template or with collaborating components.
|
|
|
|
|
The Angular Testing Platform is specifically designed to facilitate such tests.
|
|
|
|
|
|
|
|
|
|
Consider this `ButtonComp` component.
|
|
|
|
|
+makeExample('testing/ts/app/bag/bag.ts', 'ButtonComp', 'app/bag/bag.ts (ButtonComp)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
The following ATP test demonstrates that clicking a button in the template leads
|
|
|
|
|
to an update of the on-screen message.
|
|
|
|
|
+makeExample('testing/ts/app/bag/bag.spec.ts', 'ButtonComp', 'app/bag/bag.spec.ts (ButtonComp)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
The assertions verify the data binding flow from one HTML control (the `<button>`) to the component and
|
|
|
|
|
from the component back to a _different_ HTML control (the `<span>`).
|
|
|
|
|
A passing test means the component and its template are wired up correctly.
|
|
|
|
|
|
|
|
|
|
Tests _without_ the ATP can more rapidly probe a component at its API boundary,
|
|
|
|
|
exploring many more conditions with less effort.
|
|
|
|
|
|
|
|
|
|
Here are a set of _unit tests_ that verify the component's outputs in the face of a variety of
|
|
|
|
|
component inputs.
|
|
|
|
|
+makeExample('testing/ts/app/bag/bag.no-testbed.spec.ts', 'ButtonComp', 'app/bag/bag.no-testbed.spec.ts (ButtonComp)')(format='.')
|
|
|
|
|
:marked
|
|
|
|
|
Isolated component tests offer a lot of test coverage with less code and almost no setup.
|
|
|
|
|
This advantage is even more pronounced with complex components that
|
|
|
|
|
require meticulous preparation with the Angular Testing Platform.
|
|
|
|
|
|
|
|
|
|
On the other hand, isolated unit tests can't confirm that the `ButtonComp` is
|
|
|
|
|
properly bound to its template or even data bound at all.
|
|
|
|
|
Use ATP tests for that.
|
|
|
|
|
|
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
|
|
|
|
|
.l-hr
|
|
|
|
|
|
|
|
|
|
a#atp-api
|
|
|
|
|
:marked
|
|
|
|
|
# Angular Testing Platform APIs
|
|
|
|
|
|
|
|
|
|
This section takes inventory of the most useful _Angular Testing Platform_ features and summarizes what they do.
|
|
|
|
|
|
|
|
|
|
The _Angular Testing Platform_ consists of the `TestBed` and `ComponentFixture` classes plus a handful of functions in the test environment.
|
|
|
|
|
The [_TestBed_](#testbed-api-summary) and [_ComponentFixture_](#componentfixture-api-summary) classes are covered separately.
|
|
|
|
|
|
|
|
|
|
Here's a summary of the functions, in order of likely utility:
|
|
|
|
|
|
|
|
|
|
table
|
|
|
|
|
tr
|
|
|
|
|
th Function
|
|
|
|
|
th Description
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>async</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Runs the body of a test (`it`) or setup (`beforeEach`) function within a special _async test zone_.
|
|
|
|
|
See [here](#async-fn-in-it) and [here](#async-fn-in-before-each).
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>fakeAsync</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Runs the body of a test (`it`) within a special _fakeAsync test zone_, enabling
|
|
|
|
|
a linear control flow coding style. See [above](#fake-async).
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>tick</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Simulates the passage of time and the completion of pending asynchronous activities
|
|
|
|
|
by flushing timer and micro-task queues in the _fakeAsync test zone_.
|
|
|
|
|
|
|
|
|
|
Accepts an optional argument that moves the virtual clock forward
|
|
|
|
|
the specified number of milliseconds,
|
|
|
|
|
clearing asynchronous activities scheduled within that timeframe.
|
|
|
|
|
See [above](#tick).
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>inject</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Injects one or more services from the current `TestBed` injector into a test function.
|
|
|
|
|
See [above](#inject).
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>discardPeriodicTasks</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
When a `fakeAsync` test ends with pending timer event tasks (queued `setTimeOut` and `setInterval` callbacks),
|
|
|
|
|
the test fails with a clear error message.
|
|
|
|
|
|
|
|
|
|
In general, a test should end with no queued tasks.
|
|
|
|
|
When pending timer tasks are expected, call `discardPeriodicTasks` to flush the queues
|
|
|
|
|
and avoid the error.
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>flushMicrotasks</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
When a `fakeAsync` test ends with pending "microtasks" such as unresolved promises,
|
|
|
|
|
the test fails with a clear error message.
|
|
|
|
|
|
|
|
|
|
In general, a test should wait for microtasks to finish.
|
|
|
|
|
When pending microtasks are expected, call `discardPeriodicTasks` to flush the queues
|
|
|
|
|
and avoid the error.
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>ComponentFixtureAutoDetect</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
A provider token for setting the default _auto-changeDetect_ from its default of `false`.
|
|
|
|
|
See [automatic change detection](#automatic-change-detection)
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>getTestBed</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Gets the current instance of the `TestBed`.
|
|
|
|
|
Usually unnecessary because the static class methods of the `TestBed` class are typically sufficient.
|
|
|
|
|
The `TestBed` instance exposes a few rarely used members that are not available as
|
|
|
|
|
static methods.
|
|
|
|
|
|
|
|
|
|
.l-hr
|
|
|
|
|
|
|
|
|
|
a#testbed-class-summary
|
|
|
|
|
:marked
|
|
|
|
|
# _TestBed_ Class Summary
|
|
|
|
|
The `TestBed` class API is quite large and can be overwhelming until you've explored it first
|
|
|
|
|
a little at a time. Read the early part of this chapter first
|
|
|
|
|
to get the basics before trying to absorb the full API.
|
|
|
|
|
|
|
|
|
|
.alert.is-important
|
|
|
|
|
:marked
|
|
|
|
|
The _TestBed_ is officially _experimental_ and thus subject to change.
|
|
|
|
|
Consult the [API reference](../api/core/testing/index/TestBed-class.html) for the latest status.
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
The module definition passed to `configureTestingModule`,
|
|
|
|
|
is a subset of the `@NgModule` metadata properties.
|
|
|
|
|
code-example(format="." language="javascript").
|
|
|
|
|
type TestModuleMetadata = {
|
|
|
|
|
providers?: any[];
|
|
|
|
|
declarations?: any[];
|
|
|
|
|
imports?: any[];
|
|
|
|
|
schemas?: Array<SchemaMetadata | any[]>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
Each overide method takes a `MetadataOverride<T>` where `T` is the kind of metadata
|
|
|
|
|
appropriate to the method, the parameter of an `@NgModule`, `@Component`, `@Directive`, or `@Pipe`.
|
|
|
|
|
|
|
|
|
|
code-example(format="." language="javascript").
|
|
|
|
|
type MetadataOverride<T> = {
|
|
|
|
|
add?: T;
|
|
|
|
|
remove?: T;
|
|
|
|
|
set?: T;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
a#testbed-methods
|
|
|
|
|
:marked
|
|
|
|
|
The `TestBed` API consists of static class methods that either update or reference a _global_ instance of the`TestBed`.
|
|
|
|
|
|
|
|
|
|
Internally, all static methods cover methods of the current runtime `TestBed` instance that is also returned by the `getTestBed()` function.
|
|
|
|
|
|
|
|
|
|
Call `TestBed` methods _within_ a `BeforeEach()` to ensure a fresh start before each individual test.
|
|
|
|
|
|
|
|
|
|
Here are the most important static methods, in order of likely utility.
|
|
|
|
|
table
|
|
|
|
|
tr
|
|
|
|
|
th Methods
|
|
|
|
|
th Description
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>configureTestingModule</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The testing shims (`karma-test-shim`, `browser-test-shim`)
|
|
|
|
|
establish the [initial test environment](#a#testbed-initTestEnvironment) and a default test module.
|
|
|
|
|
The default test module is configured with basic declaratives and some Angular service substitutes (e.g. `DebugDomRender`)
|
|
|
|
|
that every tester needs.
|
|
|
|
|
|
|
|
|
|
Call `configureTestingModule` to refine the test module configuration for a particular set of tests
|
|
|
|
|
by adding and removing imports, declarations (of components, directives, and pipes), and providers.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>compileComponents</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Compile the test module asynchronously after you've finished configuring it.
|
|
|
|
|
You **must** call this method if _any_ of the test module components have a `templateUrl`
|
|
|
|
|
or `styleUrls` because fetching component template and style files is necessarily asynchronous.
|
|
|
|
|
See [above](#compile-components).
|
|
|
|
|
|
|
|
|
|
Once called, the `TestBed` configuration is frozen for the duration of the current spec.
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>createComponent<T></code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Create an instance of a component of type `T` based on the current `TestBed` configuration.
|
|
|
|
|
Once called, the `TestBed` configuration is frozen for the duration of the current spec.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>overrideModule</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Replace metadata for the given `NgModule`. Recall that modules can import other modules.
|
|
|
|
|
The `overrideModule` method can reach deeply into the current test module to
|
|
|
|
|
modify one of these inner modules.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>overrideComponent</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Replace metadata for the given component class which could be nested deeply
|
|
|
|
|
within an inner module.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>overrideDirective</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Replace metadata for the given directive class which could be nested deeply
|
|
|
|
|
within an inner module.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>overridePipe</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Replace metadata for the given pipe class which could be nested deeply
|
|
|
|
|
within an inner module.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top").
|
|
|
|
|
<a id="testbed-get"></a>
|
|
|
|
|
<code>get</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Retrieve a service from the current `TestBed` injector.
|
|
|
|
|
|
|
|
|
|
The `inject` function is often adequate for this purpose.
|
|
|
|
|
But `inject` throws an error if it can't provide the service.
|
|
|
|
|
What if the service is optional?
|
|
|
|
|
|
|
|
|
|
The `TestBed.get` method takes an optional second parameter,
|
|
|
|
|
the object to return if Angular can't find the provider
|
|
|
|
|
(`null` in this example):
|
|
|
|
|
+makeExample('testing/ts/app/bag/bag.spec.ts', 'testbed-get')(format=".")
|
|
|
|
|
:marked
|
|
|
|
|
Once called, the `TestBed` configuration is frozen for the duration of the current spec.
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top").
|
|
|
|
|
<a id="testbed-initTestEnvironment"></a>
|
|
|
|
|
<code>initTestEnvironment</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Initialize the testing environment for the entire test run.
|
|
|
|
|
|
|
|
|
|
The testing shims (`karma-test-shim`, `browser-test-shim`) call it for you
|
|
|
|
|
so there is rarely a reason for you to call it yourself.
|
|
|
|
|
|
|
|
|
|
This method may be called _exactly once_. Call `resetTestEnvironment` first
|
|
|
|
|
if you absolutely need to change this default in the middle of your test run.
|
|
|
|
|
|
|
|
|
|
Specify the Angular compiler factory, a `PlatformRef`, and a default Angular test module.
|
|
|
|
|
Test modules and platforms for individual platforms are available from
|
|
|
|
|
`angular2/platform/testing/<platform_name>`.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>resetTestEnvironment</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Reset the initial test environment including the default test module.
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
A few of the `TestBed` instance methods are not covered by static `TestBed` _class_ methods.
|
|
|
|
|
These are rarely needed.
|
|
|
|
|
|
|
|
|
|
a#componentfixture-api-summary
|
|
|
|
|
:marked
|
|
|
|
|
## The _ComponentFixture_
|
|
|
|
|
|
|
|
|
|
The `TestBed.createComponent<T>`
|
|
|
|
|
creates an instance of the component `T`
|
|
|
|
|
and returns a strongly typed `ComponentFixture` for that component.
|
|
|
|
|
|
|
|
|
|
The `ComponentFixture` properties and methods provide access to the component,
|
|
|
|
|
its DOM representation, and aspects of its Angular environment.
|
|
|
|
|
|
|
|
|
|
a#componentfixture-properties
|
|
|
|
|
:marked
|
|
|
|
|
### _ComponentFixture_ properties
|
|
|
|
|
|
|
|
|
|
Here are the most important properties for testers, in order of likely utility.
|
|
|
|
|
|
|
|
|
|
table
|
|
|
|
|
tr
|
|
|
|
|
th Properties
|
|
|
|
|
th Description
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>componentInstance</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The instance of the component class created by `TestBed.createComponent`.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>debugElement</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The `DebugElement` associated with the root element of the component.
|
|
|
|
|
|
|
|
|
|
The `debugElement` provides insight into the component and its DOM element during test and debugging.
|
|
|
|
|
It's a critical property for testers. The most interesting members are covered [below](#debugelement-details).
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>nativeElement</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The native DOM element at the root of the component.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>changeDetectorRef</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The `ChangeDetectorRef` for the component.
|
|
|
|
|
|
|
|
|
|
The `ChangeDetectorRef` is most valuable when testing a
|
|
|
|
|
component that has the `ChangeDetectionStrategy.OnPush`
|
|
|
|
|
or the component's change detection is under your programmatic control.
|
|
|
|
|
|
|
|
|
|
a#componentfixture-methods
|
|
|
|
|
:marked
|
|
|
|
|
### _ComponentFixture_ methods
|
|
|
|
|
|
|
|
|
|
The _fixture_ methods cause Angular to perform certain tasks to the component tree.
|
|
|
|
|
Call these method to trigger Angular behavior in response to simulated user action.
|
|
|
|
|
|
|
|
|
|
Here are the most useful methods for testers.
|
|
|
|
|
table
|
|
|
|
|
tr
|
|
|
|
|
th Methods
|
|
|
|
|
th Description
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>detectChanges</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Trigger a change detection cycle for the component.
|
|
|
|
|
|
|
|
|
|
Call it to initialize the component (it calls `ngOnInit`) and after your
|
|
|
|
|
test code change the component's data bound property values.
|
|
|
|
|
Angular can't see that you've changed `personComponent.name` and won't update the `name`
|
|
|
|
|
binding until you call `detectChanges`.
|
|
|
|
|
|
|
|
|
|
Runs `checkNoChanges`afterwards to confirm there are no circular updates unless
|
|
|
|
|
called as `detectChanges(false)`;
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>autoDetectChanges</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Set whether the fixture should try to detect changes automatically.
|
|
|
|
|
|
|
|
|
|
When autodetect is true, the test fixture listens for _zone_ events and calls `detectChanges`.
|
|
|
|
|
You probably still have to call `fixture.detectChanges` to trigger data binding updates
|
|
|
|
|
when your test code modifies component property values directly.
|
|
|
|
|
|
|
|
|
|
The default is `false` and testers who prefer fine control over test behavior
|
|
|
|
|
tend to keep it `false`.
|
|
|
|
|
|
|
|
|
|
Calls `detectChanges` immediately which detects existing changes
|
|
|
|
|
and will trigger `ngOnInit` if the component has not yet been initialized.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>checkNoChanges</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Do a change detection run to make sure there are no pending changes.
|
|
|
|
|
Throws an exceptions if there are.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>isStable</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Return `true` if the fixture is currently _stable_.
|
|
|
|
|
Returns `false` if there are async tasks that have not completed.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>whenStable</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Returns a promise that resolves when the fixture is stable.
|
|
|
|
|
|
|
|
|
|
Hook that promise to resume testing after completion of asynchronous activity or
|
|
|
|
|
asynchronous change detection.
|
|
|
|
|
See [above](#when-stable)
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>destroy</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Trigger component destruction.
|
|
|
|
|
|
|
|
|
|
a#debugelement-details
|
|
|
|
|
:marked
|
|
|
|
|
### _DebugElement_
|
|
|
|
|
|
|
|
|
|
The `DebugElement` provides crucial insights into the component's DOM representation.
|
|
|
|
|
|
|
|
|
|
From the test root component's `DebugElement`, returned by `fixture.debugElement`,
|
|
|
|
|
you can walk (and query) the fixture's entire element and component sub-trees.
|
|
|
|
|
|
|
|
|
|
.alert.is-important
|
|
|
|
|
:marked
|
|
|
|
|
The _DebugElement_ is officially _experimental_ and thus subject to change.
|
|
|
|
|
Consult the [API reference](../api/core/index/DebugElement-class.html) for the latest status.
|
|
|
|
|
:marked
|
|
|
|
|
Here are the most useful `DebugElement` members for testers in approximate order of utility.
|
|
|
|
|
|
|
|
|
|
table
|
|
|
|
|
tr
|
|
|
|
|
th Member
|
|
|
|
|
th Description
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>nativeElement</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The corresponding DOM element in the browser (null for WebWorkers).
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>query</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Calling `query(predicate: Predicate<DebugElement>)` returns the first `DebugElement`
|
|
|
|
|
that matches the [predicate](#query-predicate) at any depth in the subtree.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>queryAll</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Calling `queryAll(predicate: Predicate<DebugElement>)` returns all `DebugElements`
|
|
|
|
|
that matches the [predicate](#query-predicate) at any depth in subtree.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>injector</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The host dependency injector.
|
|
|
|
|
For example, the root element's component instance injector.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>componentInstance</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The element's own component instance, if it has one.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>context</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
An object that provides parent context for this element.
|
|
|
|
|
Often an ancestor component instance that governs this element.
|
|
|
|
|
|
|
|
|
|
When an element is repeated with in `*ngFor`, the context is an `NgForRow` whose `$implicit`
|
|
|
|
|
property is the value of the row instance value.
|
|
|
|
|
For example, the `hero` in `*ngFor="let hero of heroes"`.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>children</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The immediate `DebugElement` children. Walk the tree by descending through `children`.
|
|
|
|
|
|
|
|
|
|
.l-sub-section
|
|
|
|
|
:marked
|
|
|
|
|
`DebugElement` also has `childNodes`, a list of `DebugNode` objects.
|
|
|
|
|
`DebugElement` derives from `DebugNode` objects and there are often
|
|
|
|
|
more nodes than elements. Testers can usually ignore plain nodes.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>parent</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The `DebugElement` parent. Null if this is the root element.
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>name</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The element tag name, if it is an element.
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>triggerEventHandler</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Triggers the event by its name if there is a corresponding listener
|
|
|
|
|
in the element's `listeners` collection.
|
|
|
|
|
|
|
|
|
|
If the event lacks a listner or there's some other problem,
|
|
|
|
|
consider calling `nativeElement.dispatchEvent(eventObject)`
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>listeners</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
The callbacks attached to the component's `@Output` properties and/or the element's event properties.
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>providerTokens</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
This component's injector lookup tokens.
|
|
|
|
|
Includes the component itself plus the tokens that the component lists in its `providers` metadata.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>source</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Where to find this element in the source component template.
|
|
|
|
|
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>references</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Dictionary of objects associated with template local variables (e.g. `#foo`),
|
|
|
|
|
keyed by the local variable name.
|
|
|
|
|
|
|
|
|
|
a#query-predicate
|
|
|
|
|
:marked
|
|
|
|
|
The `DebugElement.query(predicate)` and `DebugElement.queryAll(predicate)` methods take a
|
|
|
|
|
predicate that filters the source element's subtree for matching `DebugElement`.
|
|
|
|
|
|
|
|
|
|
The predicate is any method that takes a `DebugElement` and returns a _truthy_ value.
|
|
|
|
|
The following example finds all `DebugElements` with a reference to a template local variable named "content":
|
|
|
|
|
+makeExample('testing/ts/app/bag/bag.spec.ts', 'custom-predicate')(format=".")
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
The Angular `By` class has three static methods for common predicates:
|
|
|
|
|
* `By.all` - return all elements
|
|
|
|
|
* `By.css(selector)` - return elements with matching CSS selectors.
|
|
|
|
|
* `By.directive(directive)` - return elements that Angular matched to an instance of the directive class.
|
|
|
|
|
|
|
|
|
|
+makeExample('testing/ts/app/hero/hero-list.component.spec.ts', 'by', 'app/hero/hero-list.component.spec.ts')(format=".")
|
|
|
|
|
|
|
|
|
|
a#renderer-tests
|
|
|
|
|
:marked
|
|
|
|
|
Many custom application directives inject the `Renderer` and call one of its `set...` methods.
|
|
|
|
|
|
|
|
|
|
The test environment substitutes the `DebugDomRender` for the runtime `Renderer`.
|
|
|
|
|
The `DebugDomRender` updates additional dictionary properties of the `DebugElement`
|
|
|
|
|
when something calls a `set...` method.
|
|
|
|
|
|
|
|
|
|
These dictionary properties are primarily of interest to authors of Angular DOM inspection tools
|
|
|
|
|
but they may provide useful insights to testers as well.
|
|
|
|
|
|
|
|
|
|
table
|
|
|
|
|
tr
|
|
|
|
|
th Dictionary
|
|
|
|
|
th Description
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>properties</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Updated by `Renderer.setElementProperty`.
|
|
|
|
|
Many Angular directives call it, including `NgModel`.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>attributes</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Updated by `Renderer.setElementAttribute`.
|
|
|
|
|
Angular `[attribute]` bindings call it.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>classes</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Updated by `Renderer.setElementClass`.
|
|
|
|
|
Angular `[class]` bindings call it.
|
|
|
|
|
tr
|
|
|
|
|
td(style="vertical-align: top") <code>styles</code>
|
|
|
|
|
td
|
|
|
|
|
:marked
|
|
|
|
|
Updated by `Renderer.setElementStyle`.
|
|
|
|
|
Angular `[style]` bindings call it.
|
|
|
|
|
:marked
|
|
|
|
|
Here's an example of `Renderer` tests from the <live-example plnkr="bag-specs">live "Specs Bag" sample</live-example>.
|
|
|
|
|
|
|
|
|
|
+makeExample('testing/ts/app/bag/bag.spec.ts', 'debug-dom-renderer')(format=".")
|
|
|
|
|
|
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
|
|
|
|
|
.l.hr
|
|
|
|
|
|
|
|
|
|
a#faq
|
|
|
|
|
.l-main-section
|
|
|
|
|
:marked
|
|
|
|
|
## FAQ: Frequently Asked Questions
|
|
|
|
|
//
|
|
|
|
|
:marked
|
|
|
|
|
General
|
|
|
|
|
* [When are end-to-end (e2e) tests a good choice?](#q-when-e2e)
|
|
|
|
|
* [When to use the _TestBed_?](#q-why-testbed)
|
|
|
|
|
* [When to write vanilla tests without the _TestBed_?](#q-when-no-testbed)
|
|
|
|
|
* [When can I skip _TestBed.compileComponents_?](#q-when-no-compile-components)
|
|
|
|
|
* [Why must _TestBed.compileComponents_ be called last?](#q-why-compile-components-is-last)
|
|
|
|
|
* [Why must _inject_ be called last?](#q-why-last-last)
|
|
|
|
|
* [What's the difference between _async_ and _fakeAsync_?](#q-async-vs-fake-async)
|
|
|
|
|
* [What's the difference between _whenStable_ and _tick_?](#q-when-stable-vs-tick)
|
|
|
|
|
* [How do I get something from the component's injector?](#q-component-injector)
|
|
|
|
|
* [Why do feature modules make testing easier?](#q-why-feature-modules)
|
|
|
|
|
* [When should I prefer the _DynamicTestModule_?](#q-dynamic-test-module)
|
|
|
|
|
* [How do I know if an injected service method was called?](#q-spy-on-service)
|
|
|
|
|
* [When must I call _detectChanges_ and why?](#q-detect-changes)
|
|
|
|
|
* [What's the difference between _triggerEventHandler_ and _dispatchEvent_?](#q-trigger-event-handler-vs-dispatch-event)
|
|
|
|
|
* [How do I find an element by directive?](#q-by-directive)
|
|
|
|
|
* [How do I extend Jasmine matchers?](#q-jasmine-matchers)
|
|
|
|
|
* [Why would I add a test folder and how?](#q-test-folder)
|
|
|
|
|
* [Why put specs next to the things they test?](#q-spec-file-location)
|
|
|
|
|
* [When would I put specs in a test folder?](#q-specs-in-test-folder)
|
|
|
|
|
* [How do I use the Jasmine HTML TestRunner in the browser?](#q-jasmine-browser-test-runner)
|
|
|
|
|
|
|
|
|
|
Resources
|
|
|
|
|
* [Where can I learn more about unit testing in JavaScript?](#q-js-unit-testing-resources)
|
|
|
|
|
* [Where can I learn more about testing with Jasmine?](#q-jasmine-resources)
|
|
|
|
|
* [Where can I learn more about testing with karma?](#q-karma-resources)
|
|
|
|
|
* [Where can I learn more about e2e testing with protractor?](#q-protractor-resources)
|
|
|
|
|
|
|
|
|
|
a(href="#top").to-top Back to top
|
|
|
|
|
|
|
|
|
|
.l-hr
|
|
|
|
|
|
|
|
|
|
a#q-spec-file-location
|
|
|
|
|
:marked
|
|
|
|
|
### Why put specs next to the things they test?
|
|
|
|
|
|
|
|
|
|
We recommend putting unit test spec files in the same folder
|
|
|
|
|
as the application source code files that they test because
|
|
|
|
|
- Such tests are easy to find
|
|
|
|
|
- You see at a glance if a part of our application lacks tests.
|
|
|
|
|
- Nearby tests can reveal how a part works in context.
|
|
|
|
|
- When you move the source (inevitable), you remember to move the test.
|
|
|
|
|
- When you rename the source file (inevitable), you remember to rename the test file.
|
|
|
|
|
|
|
|
|
|
.l-hr
|
|
|
|
|
|
|
|
|
|
a#q-specs-in-test-folder
|
|
|
|
|
:marked
|
|
|
|
|
### When would I put specs in a test folder?
|
|
|
|
|
|
|
|
|
|
Application integration specs can test the interactions of multiple parts
|
|
|
|
|
spread across folders and modules.
|
|
|
|
|
They don't really belong to part in particular so they don't have a
|
|
|
|
|
natural home next to any one file.
|
|
|
|
|
|
|
|
|
|
It's often better to create an appropriate folder for them in the `tests` directory.
|
|
|
|
|
|
|
|
|
|
Of course specs that test the test helpers belong in the `test` folder,
|
|
|
|
|
next to their corresponding helper files.
|