docs(testing): explain rationale for stubbed routerLink tests (#2422)

This commit is contained in:
Ward Bell 2016-09-20 13:45:50 -07:00 committed by GitHub
parent 0f99a7c130
commit 55bf126489
1 changed files with 82 additions and 65 deletions

View File

@ -11,7 +11,7 @@ block includes
Along the way you will learn some general testing principles and techniques but the focus is on
Angular testing.
a#top
#top
:marked
# Table of Contents
1. [Introduction to Angular Testing](#testing-intro)
@ -83,7 +83,7 @@ a#top
a(href="#top").to-top Back to top
.l-hr
a#testing-intro
#testing-intro
:marked
# Introduction to Angular Testing
@ -154,7 +154,7 @@ table(width="100%")
and assert that the application responds in the browser as expected.
.l-hr
a#setup
#setup
:marked
# Setup
@ -184,7 +184,7 @@ a#setup
you can skip the rest of this section and get on with your first test.
The QuickStart repo provides all necessary setup.
a#setup-files
#setup-files
:marked
### Setup files
Here's brief description of the setup files.
@ -247,7 +247,7 @@ table(width="100%")
They were installed when you ran `npm install`.
.l-hr
a#1st-karma-test
#1st-karma-test
:marked
# The first karma test
@ -368,7 +368,7 @@ figure.image-display
a(href="#top").to-top Back to top
.l-hr
a#atp-intro
#atp-intro
:marked
# The Angular Testing Platform (ATP)
@ -439,7 +439,7 @@ a(href="#top").to-top Back to top
.l-hr
a#sample-app
#sample-app
:marked
# The sample application and its tests
@ -460,7 +460,7 @@ a#sample-app
a(href="#top").to-top Back to top
.l-hr
a#simple-component-test
#simple-component-test
:marked
# Test a component
@ -476,7 +476,7 @@ a#simple-component-test
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='.')
a#configure-testing-module
#configure-testing-module
:marked
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 (setup)')(format='.')
@ -492,7 +492,7 @@ a#configure-testing-module
But that would lead to tons more configuration in order to support the other components within `AppModule`
that have nothing to do with `BannerComponent`.
a#create-component
#create-component
:marked
`TestBed.createComponent` creates an instance of `BannerComponent` to test.
The method returns a `ComponentFixture`, a handle on the test environment surrounding the created component.
@ -516,7 +516,7 @@ a#create-component
:markdown
These tests ask the `DebugElement` for the native HTML element to satisfy their expectations.
a#detect-changes
#detect-changes
:marked
### _detectChanges_: Angular change detection under test
@ -540,7 +540,7 @@ a#detect-changes
It gives the tester an opportunity to investigate the state of
the component _before Angular initiates data binding or calls lifecycle hooks_.
a#auto-detect-changes
#auto-detect-changes
:marked
### Automatic change detection
Some testers prefer that the Angular test environment run change detection automatically.
@ -570,7 +570,7 @@ a(href="#top").to-top Back to top
.l-hr
a#component-with-dependency
#component-with-dependency
:marked
# Test a component with a dependency
Components often have service dependencies.
@ -605,7 +605,7 @@ a#component-with-dependency
and its tests:
+makeExample('testing/ts/app/welcome.component.spec.ts', 'user-service-stub')(format='.')
a#injected-service-reference
#injected-service-reference
:marked
## Referencing injected services
The tests need access to the injected (stubbed) `UserService`.
@ -641,7 +641,7 @@ a#injected-service-reference
.alert.is-important
:marked
Use the component's own injector to get the component's injected service.
a#welcome-spec-setup
#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='.')
@ -658,7 +658,7 @@ a(href="#top").to-top Back to top
.l-hr
a#component-with-async-service
#component-with-async-service
:marked
# Test a component with an async service
Many services return values asynchronously.
@ -677,7 +677,7 @@ a#component-with-async-service
They should emulate 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
#service-spy
:marked
### Spying on the real service
@ -709,7 +709,7 @@ a#service-spy
The test must become an "async test" ... like the third test
a#async
#async
:marked
## The _async_ function in _it_
@ -731,7 +731,7 @@ a#async
Some functions called within a test (such as `fixture.whenStable`) continue to reveal their asynchronous behavior.
Consider also the [_fakeAsync_](#fake-async) alternative which affords a more linear coding experience.
a#when-stable
#when-stable
:marked
## _whenStable_
The test must wait for the `getQuote` promise to resolve.
@ -754,8 +754,8 @@ a#when-stable
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
#fakeAsync
#fake-async
:marked
## The _fakeAsync_ function
@ -776,8 +776,8 @@ a#fake-async
There are limitations. For example, you cannot make an XHR call from within a `fakeAsync`.
a#tick
a#tick-first-look
#tick
#tick-first-look
:marked
## The _tick_ function
Compare the third and fourth tests. Notice that `fixture.whenStable` is gone, replaced by `tick()`.
@ -795,7 +795,7 @@ a#tick-first-look
To more fully appreciate the improvement, imagine a succession of asynchronous operations,
chained in a long sequence of promise callbacks.
a#jasmine-done
#jasmine-done
:marked
## _jasmine.done_
@ -819,7 +819,7 @@ a(href="#top").to-top Back to top
.l-hr
a#component-with-external-template
#component-with-external-template
:marked
# Test a component with an external template
The `TestBed.createComponent` is a synchronous method.
@ -847,7 +847,7 @@ a#component-with-external-template
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-in-before-each
#async-in-before-each
:marked
## The _async_ function in _beforeEach_
@ -855,7 +855,7 @@ a#async-in-before-each
The `async` function arranges for the tester's code to run in a special _async test zone_
that hides the mechanics of asynchronous execution, just as it does when passed to an [_it_ test)(#async).
a#compile-components
#compile-components
:marked
## _compileComponents_
In this example, `Testbed.compileComponents` compiles one component, the `DashboardComponent`.
@ -884,7 +884,7 @@ a#compile-components
.l-hr
a#component-with-inputs-outputs
#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.
@ -971,7 +971,7 @@ a#component-with-inputs-outputs
.l-hr
a#component-inside-test-host
#component-inside-test-host
:marked
# Test a component inside a test host component
@ -1015,7 +1015,7 @@ a(href="#top").to-top Back to top
.l-hr
a#routed-component
#routed-component
:marked
# Test a routed component
@ -1041,7 +1041,7 @@ a#routed-component
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
#inject
:marked
## The _inject_ function
@ -1083,7 +1083,7 @@ a(href="#top").to-top Back to top
.l-hr
a#routed-component-w-param
#routed-component-w-param
:marked
# Test a routed component with parameters
@ -1117,7 +1117,7 @@ a#routed-component-w-param
By now you know how to stub the `Router` and a data service.
Stubbing the `ActivatedRoute` would follow the same pattern except for a complication:
the `ActivatedRoute.params` is an _Observable_.
a#stub-observable
#stub-observable
:marked
### _Observable_ test double
@ -1148,7 +1148,7 @@ a#stub-observable
:marked
The router stubs in this chapter are meant to inspire you. Create your own stubs to fit your testing needs.
a#observable-tests
#observable-tests
:marked
### _Observable_ tests
Here's a test demonstrating the component's behavior when the observed `id` refers to an existing hero:
@ -1177,7 +1177,7 @@ a#observable-tests
.l-hr
a#page-object
#page-object
:marked
# Use a _page_ object to simplify setup
@ -1214,24 +1214,23 @@ figure.image-display
a(href="#top").to-top Back to top
.l-hr
a#router-outlet-component
#router-outlet-component
:marked
# Test a _RouterOutlet_ component
The `AppComponent` displays routed components in a `<router-outlet>`.
It also displays a navigation bar with anchors and their `RouterLink` directives.
a#app-component-html
#app-component-html
+makeExample('testing/ts/app/app.component.html', '', 'app/app.component.html')(format='.')
:marked
The component class does nothing.
+makeExample('testing/ts/app/app.component.ts', '', 'app/app.component.ts')(format='.')
.l-sub-section
:marked
This component may not seem worth testing but it's instructive to show how to test it.
:marked
Unit tests can confirm that the anchors are wired properly without engaging the router.
See why this is worth doing [below](#why-stubbed-routerlink-tests).
a#stub-component
#stub-component
:marked
### Stubbing unneeded components
@ -1251,7 +1250,7 @@ a#stub-component
The component stubs are essential.
Without them, the Angular compiler doesn't recognize the `<app-welcome>` and `<router-outlet>` tags
and throws an error.
a#router-link-stub
#router-link-stub
:marked
### Stubbing the _RouterLink_
@ -1263,8 +1262,8 @@ a#router-link-stub
Clicking the anchor should trigger the `onClick` method which sets the telltale `navigatedTo` property.
Tests can inspect that property to confirm the expected _click-to-navigation_ behavior.
a#by-directive
a#inject-directive
#by-directive
#inject-directive
:marked
### _By.directive_ and injected directives
@ -1278,7 +1277,7 @@ a#inject-directive
1. You can use the component's dependency injector to get an attached directive because
Angular always adds attached directives to the component's injector.
a#app-component-tests
#app-component-tests
:marked
Here are some tests that leverage this setup:
+makeExample('testing/ts/app/app.component.spec.ts', 'tests', 'app/app.component.spec.ts (selected tests)')(format='.')
@ -1295,10 +1294,28 @@ a#app-component-tests
This is a skill you may need to test a more sophisticated component, one that changes the display,
re-calculates parameters, or re-arranges navigation options when the user clicks the link.
#why-stubbed-routerlink-tests
:marked
### What good are these tests?
Stubbed `RouterLink` tests can confirm that a component with links and an outlet is setup properly,
that the component has the links it should have and
that they are all pointing in the expected direction.
These tests do not concern whether the app will succeed in navigating to the target component when the user clicks a link.
Stubbing the RouterLink and RouterOutlet is the best option for such limited testing goals.
Relying on the real router would make them brittle.
They could fail for reasons unrelated to the component.
For example, a navigation guard could prevent an unauthorized user from visiting the `HeroListComponent`.
That's not the fault of the `AppComponent` and no change to that component could cure the failed test.
A _different_ battery of tests can explore whether the application navigates as expected
in the presence of conditions that influence guards such as whether the user is authenticated and authorized.
A future chapter update will explain how to write such tests with the `RouterTestingModule`.
a(href="#top").to-top Back to top
.l-hr
a#shallow-component-test
#shallow-component-test
:marked
# "Shallow component tests" with *NO\_ERRORS\_SCHEMA*
@ -1332,7 +1349,7 @@ a(href="#top").to-top Back to top
.l-hr
a#attribute-directive
#attribute-directive
:marked
# Test an attribute directive
@ -1392,8 +1409,8 @@ a(href="#top").to-top Back to top
.l-hr
a#isolated-tests
a#testing-without-atp
#isolated-tests
#testing-without-atp
:marked
# Testing without the Angular Testing Platform
@ -1423,7 +1440,7 @@ a#testing-without-atp
Write _Angular_ tests to validate the part as it interacts with Angular,
updates the DOM, and collaborates with the rest of the application.
a#isolated-service-tests
#isolated-service-tests
:marked
## Services
Services are good candidates for isolated unit testing.
@ -1474,7 +1491,7 @@ a#isolated-service-tests
Use the Angular Testing Platform when writing tests that validate how a service interacts with components
_within the Angular runtime environment_.
a#isolated-pipe-tests
#isolated-pipe-tests
:marked
## Pipes
Pipes are easy to test without the Angular Testing Platform (ATP).
@ -1500,7 +1517,7 @@ a#isolated-pipe-tests
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
#isolated-component-tests
:marked
## Components
@ -1537,7 +1554,7 @@ a(href="#top").to-top Back to top
.l-hr
a#atp-api
#atp-api
:marked
# Angular Testing Platform APIs
@ -1629,7 +1646,7 @@ table
.l-hr
a#testbed-class-summary
#testbed-class-summary
:marked
# _TestBed_ Class Summary
The `TestBed` class is a principle feature of the _Angular Testing Platform_.
@ -1659,7 +1676,7 @@ code-example(format="." language="javascript").
};
:marked
a#testbed-methods
#testbed-methods
:marked
The `TestBed` API consists of static class methods that either update or reference a _global_ instance of the`TestBed`.
@ -1677,7 +1694,7 @@ table
td
:marked
The testing shims (`karma-test-shim`, `browser-test-shim`)
establish the [initial test environment](#a#testbed-initTestEnvironment) and a default testing module.
establish the [initial test environment](##testbed-initTestEnvironment) and a default testing module.
The default testing module is configured with basic declaratives and some Angular service substitutes (e.g. `DebugDomRender`)
that every tester needs.
@ -1771,7 +1788,7 @@ table
A few of the `TestBed` instance methods are not covered by static `TestBed` _class_ methods.
These are rarely needed.
a#component-fixture-api-summary
#component-fixture-api-summary
:marked
## The _ComponentFixture_
@ -1782,7 +1799,7 @@ a#component-fixture-api-summary
The `ComponentFixture` properties and methods provide access to the component,
its DOM representation, and aspects of its Angular environment.
a#component-fixture-properties
#component-fixture-properties
:marked
### _ComponentFixture_ properties
@ -1820,7 +1837,7 @@ table
component that has the `ChangeDetectionStrategy.OnPush`
or the component's change detection is under your programmatic control.
a#component-fixture-methods
#component-fixture-methods
:marked
### _ComponentFixture_ methods
@ -1887,7 +1904,7 @@ table
:marked
Trigger component destruction.
a#debug-element-details
#debug-element-details
:marked
### _DebugElement_
@ -2003,7 +2020,7 @@ table
Dictionary of objects associated with template local variables (e.g. `#foo`),
keyed by the local variable name.
a#query-predicate
#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`.
@ -2020,7 +2037,7 @@ a#query-predicate
+makeExample('testing/ts/app/hero/hero-list.component.spec.ts', 'by', 'app/hero/hero-list.component.spec.ts')(format=".")
a#renderer-tests
#renderer-tests
:marked
Many custom application directives inject the `Renderer` and call one of its `set...` methods.
@ -2068,7 +2085,7 @@ a(href="#top").to-top Back to top
.l.hr
a#faq
#faq
.l-main-section
:marked
## FAQ: Frequently Asked Questions
@ -2106,7 +2123,7 @@ a(href="#top").to-top Back to top
.l-hr
a#q-spec-file-location
#q-spec-file-location
:marked
### Why put specs next to the things they test?
@ -2120,7 +2137,7 @@ a#q-spec-file-location
.l-hr
a#q-specs-in-test-folder
#q-specs-in-test-folder
:marked
### When would I put specs in a test folder?