docs(testing): explain rationale for stubbed routerLink tests (#2422)
This commit is contained in:
parent
0f99a7c130
commit
55bf126489
|
@ -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?
|
||||
|
||||
|
|
Loading…
Reference in New Issue