{ "id": "guide/testing-components-scenarios", "title": "Component testing scenarios", "contents": "\n\n\n
This guide explores common component testing use cases.
\n For the sample app that the testing guides describe, see the
For the tests features in the testing guides, see
In the example app, the BannerComponent
presents static title text in the HTML template.
After a few changes, the BannerComponent
presents a dynamic title by binding to\nthe component's title
property like this.
As minimal as this is, you decide to add a test to confirm that component\nactually displays the right content where you think it should.
\nYou'll write a sequence of tests that inspect the value of the <h1>
element\nthat wraps the title property interpolation binding.
You update the beforeEach
to find that element with a standard HTML querySelector
\nand assign it to the h1
variable.
For your first test you'd like to see that the screen displays the default title
.\nYour instinct is to write a test that immediately inspects the <h1>
like this:
That test fails with the message:
\nBinding happens when Angular performs change detection.
\nIn production, change detection kicks in automatically\nwhen Angular creates a component or the user enters a keystroke or\nan asynchronous activity (e.g., AJAX) completes.
\nThe TestBed.createComponent
does not trigger change detection; a fact confirmed in the revised test:
You must tell the TestBed
to perform data binding by calling fixture.detectChanges()
.\nOnly then does the <h1>
have the expected title.
Delayed change detection is intentional and useful.\nIt gives the tester an opportunity to inspect and change the state of\nthe component before Angular initiates data binding and calls lifecycle hooks.
\nHere's another test that changes the component's title
property before calling fixture.detectChanges()
.
The BannerComponent
tests frequently call detectChanges
.\nSome testers prefer that the Angular test environment run change detection automatically.
That's possible by configuring the TestBed
with the ComponentFixtureAutoDetect
provider.\nFirst import it from the testing utility library:
Then add it to the providers
array of the testing module configuration:
Here are three tests that illustrate how automatic change detection works.
\nThe first test shows the benefit of automatic change detection.
\nThe second and third test reveal an important limitation.\nThe Angular testing environment does not know that the test changed the component's title
.\nThe ComponentFixtureAutoDetect
service responds to asynchronous activities such as promise resolution, timers, and DOM events.\nBut a direct, synchronous update of the component property is invisible.\nThe test must call fixture.detectChanges()
manually to trigger another cycle of change detection.
Rather than wonder when the test fixture will or won't perform change detection,\nthe samples in this guide always call detectChanges()
explicitly.\nThere is no harm in calling detectChanges()
more often than is strictly necessary.
To simulate user input, you can find the input element and set its value
property.
You will call fixture.detectChanges()
to trigger Angular's change detection.\nBut there is an essential, intermediate step.
Angular doesn't know that you set the input element's value
property.\nIt won't read that property until you raise the element's input
event by calling dispatchEvent()
.\nThen you call detectChanges()
.
The following example demonstrates the proper sequence.
\nThe BannerComponent
above is defined with an inline template and inline css, specified in the @Component.template
and @Component.styles
properties respectively.
Many components specify external templates and external css with the\n@Component.templateUrl
and @Component.styleUrls
properties respectively,\nas the following variant of BannerComponent
does.
This syntax tells the Angular compiler to read the external files during component compilation.
\nThat's not a problem when you run the CLI ng test
command because it\ncompiles the app before running the tests.
However, if you run the tests in a non-CLI environment,\ntests of this component may fail.\nFor example, if you run the BannerComponent
tests in a web coding environment such as plunker, you'll see a message like this one:
You get this test failure message when the runtime environment\ncompiles the source code during the tests themselves.
\nTo correct the problem, call compileComponents()
as explained below.
Components often have service dependencies.
\nThe WelcomeComponent
displays a welcome message to the logged in user.\nIt knows who the user is based on a property of the injected UserService
:
The WelcomeComponent
has decision logic that interacts with the service, logic that makes this component worth testing.\nHere's the testing module configuration for the spec file:
This time, in addition to declaring the component-under-test,\nthe configuration adds a UserService
provider to the providers
list.\nBut not the real UserService
.
A component-under-test doesn't have to be injected with real services.\nIn fact, it is usually better if they are test doubles (stubs, fakes, spies, or mocks).\nThe purpose of the spec is to test the component, not the service,\nand real services can be trouble.
\nInjecting the real UserService
could be a nightmare.\nThe real service might ask the user for login credentials and\nattempt to reach an authentication server.\nThese behaviors can be hard to intercept.\nIt is far easier and safer to create and register a test double in place of the real UserService
.
This particular test suite supplies a minimal mock of the UserService
that satisfies the needs of the WelcomeComponent
and its tests:
The tests need access to the (stub) UserService
injected into the WelcomeComponent
.
Angular has a hierarchical injection system.\nThere can be injectors at multiple levels, from the root injector created by the TestBed
\ndown through the component tree.
The safest way to get the injected service, the way that always works,\nis to get it from the injector of the component-under-test.\nThe component injector is a property of the fixture's DebugElement
.
You may also be able to get the service from the root injector via TestBed.inject()
.\nThis is easier to remember and less verbose.\nBut it only works when Angular injects the component with the service instance in the test's root injector.
In this test suite, the only provider of UserService
is the root testing module,\nso it is safe to call TestBed.inject()
as follows:
For a use case in which TestBed.inject()
does not work,\nsee the Override component providers section that\nexplains when and why you must get the service from the component's injector instead.
Here's the complete beforeEach()
, using TestBed.inject()
:
And here are some tests:
\nThe first is a sanity test; it confirms that the stubbed UserService
is called and working.
The second parameter to the Jasmine matcher (e.g., 'expected name'
) is an optional failure label.\nIf the expectation fails, Jasmine appends this label to the expectation failure message.\nIn a spec with multiple expectations, it can help clarify what went wrong and which expectation failed.
The remaining tests confirm the logic of the component when the service returns different values.\nThe second test validates the effect of changing the user name.\nThe third test checks that the component displays the proper message when there is no logged-in user.
\n\nIn this sample, the AboutComponent
template hosts a TwainComponent
.\nThe TwainComponent
displays Mark Twain quotes.
Note that the value of the component's quote
property passes through an AsyncPipe
.\nThat means the property returns either a Promise
or an Observable
.
In this example, the TwainComponent.getQuote()
method tells you that\nthe quote
property returns an Observable
.
The TwainComponent
gets quotes from an injected TwainService
.\nThe component starts the returned Observable
with a placeholder value ('...'
),\nbefore the service can return its first quote.
The catchError
intercepts service errors, prepares an error message,\nand returns the placeholder value on the success channel.\nIt must wait a tick to set the errorMessage
\nin order to avoid updating that message twice in the same change detection cycle.
These are all features you'll want to test.
\nWhen testing a component, only the service's public API should matter.\nIn general, tests themselves should not make calls to remote servers.\nThey should emulate such calls. The setup in this app/twain/twain.component.spec.ts
shows one way to do that:
Focus on the spy.
\nThe spy is designed such that any call to getQuote
receives an observable with a test quote.\nUnlike the real getQuote()
method, this spy bypasses the server\nand returns a synchronous observable whose value is available immediately.
You can write many useful tests with this spy, even though its Observable
is synchronous.
A key advantage of a synchronous Observable
is that\nyou can often turn asynchronous processes into synchronous tests.
Because the spy result returns synchronously, the getQuote()
method updates\nthe message on screen immediately after\nthe first change detection cycle during which Angular calls ngOnInit
.
You're not so lucky when testing the error path.\nAlthough the service spy will return an error synchronously,\nthe component method calls setTimeout()
.\nThe test must wait at least one full turn of the JavaScript engine before the\nvalue becomes available. The test must become asynchronous.
To use fakeAsync()
functionality, you must import zone.js/testing
in your test setup file.\nIf you created your project with the Angular CLI, zone-testing
is already imported in src/test.ts
.
The following test confirms the expected behavior when the service returns an ErrorObservable
.
Note that the it()
function receives an argument of the following form.
The fakeAsync()
function enables a linear coding style by running the test body in a special fakeAsync test zone
.\nThe test body appears to be synchronous.\nThere is no nested syntax (like a Promise.then()
) to disrupt the flow of control.
Limitation: The fakeAsync()
function won't work if the test body makes an XMLHttpRequest
(XHR) call.\nXHR calls within a test are rare, but if you need to call XHR, see waitForAsync()
, below.
You do have to call tick() to advance the (virtual) clock.
\nCalling tick() simulates the passage of time until all pending asynchronous activities finish.\nIn this case, it waits for the error handler's setTimeout()
.
The tick() function accepts milliseconds and tickOptions as parameters, the millisecond (defaults to 0 if not provided) parameter represents how much the virtual clock advances. For example, if you have a setTimeout(fn, 100)
in a fakeAsync()
test, you need to use tick(100) to trigger the fn callback. The tickOptions is an optional parameter with a property called processNewMacroTasksSynchronously
(defaults to true) that represents whether to invoke new generated macro tasks when ticking.
The tick() function is one of the Angular testing utilities that you import with TestBed
.\nIt's a companion to fakeAsync()
and you can only call it within a fakeAsync()
body.
In this example, we have a new macro task (nested setTimeout), by default, when we tick
, the setTimeout outside
and nested
will both be triggered.
And in some case, we don't want to trigger the new macro task when ticking, we can use tick(milliseconds, {processNewMacroTasksSynchronously: false})
to not invoke new macro task.
fakeAsync()
simulates passage of time, which allows you to calculate the difference between dates inside fakeAsync()
.
Jasmine also provides a clock
feature to mock dates. Angular automatically runs tests that are run after\njasmine.clock().install()
is called inside a fakeAsync()
method until jasmine.clock().uninstall()
is called. fakeAsync()
is not needed and throws an error if nested.
By default, this feature is disabled. To enable it, set a global flag before importing zone-testing
.
If you use the Angular CLI, configure this flag in src/test.ts
.
You can also use RxJS scheduler in fakeAsync()
just like using setTimeout()
or setInterval()
, but you need to import zone.js/plugins/zone-patch-rxjs-fake-async
to patch RxJS scheduler.\n
By default, fakeAsync()
supports the following macro tasks.
setTimeout
setInterval
requestAnimationFrame
webkitRequestAnimationFrame
mozRequestAnimationFrame
If you run other macro tasks such as HTMLCanvasElement.toBlob()
, an \"Unknown macroTask scheduled in fake async test\" error will be thrown.
If you want to support such a case, you need to define the macro task you want to support in beforeEach()
.\nFor example:
Note that in order to make the <canvas>
element Zone.js-aware in your app, you need to import the zone-patch-canvas
patch (either in polyfills.ts
or in the specific file that uses <canvas>
):
You might be satisfied with the test coverage of these tests.
\nHowever, you might be troubled by the fact that the real service doesn't quite behave this way.\nThe real service sends requests to a remote server.\nA server takes time to respond and the response certainly won't be available immediately\nas in the previous two tests.
\nYour tests will reflect the real world more faithfully if you return an asynchronous observable\nfrom the getQuote()
spy like this.
The async observable was produced by an asyncData
helper.\nThe asyncData
helper is a utility function that you'll have to write yourself, or you can copy this one from the sample code.
This helper's observable emits the data
value in the next turn of the JavaScript engine.
The RxJS defer()
operator returns an observable.\nIt takes a factory function that returns either a promise or an observable.\nWhen something subscribes to defer's observable,\nit adds the subscriber to a new observable created with that factory.
The defer()
operator transforms the Promise.resolve()
into a new observable that,\nlike HttpClient
, emits once and completes.\nSubscribers are unsubscribed after they receive the data value.
There's a similar helper for producing an async error.
\nNow that the getQuote()
spy is returning async observables,\nmost of your tests will have to be async as well.
Here's a fakeAsync()
test that demonstrates the data flow you'd expect\nin the real world.
Notice that the quote element displays the placeholder value ('...'
) after ngOnInit()
.\nThe first quote hasn't arrived yet.
To flush the first quote from the observable, you call tick().\nThen call detectChanges()
to tell Angular to update the screen.
Then you can assert that the quote element displays the expected text.
\n\nTo use waitForAsync()
functionality, you must import zone.js/testing
in your test setup file.\nIf you created your project with the Angular CLI, zone-testing
is already imported in src/test.ts
.
The TestBed.compileComponents()
method (see below) calls XHR
\nto read external template and css files during \"just-in-time\" compilation.\nWrite tests that call compileComponents()
with the waitForAsync()
utility.
Here's the previous fakeAsync()
test, re-written with the waitForAsync()
utility.
The waitForAsync()
utility hides some asynchronous boilerplate by arranging for the tester's code\nto run in a special async test zone.\nYou don't need to pass Jasmine's done()
into the test and call done()
because it is undefined
in promise or observable callbacks.
But the test's asynchronous nature is revealed by the call to fixture.whenStable()
,\nwhich breaks the linear flow of control.
When using an intervalTimer()
such as setInterval()
in waitForAsync()
, remember to cancel the timer with clearInterval()
after the test, otherwise the waitForAsync()
never ends.
The test must wait for the getQuote()
observable to emit the next quote.\nInstead of calling tick(), it calls fixture.whenStable()
.
The fixture.whenStable()
returns a promise that resolves when the JavaScript engine's\ntask queue becomes empty.\nIn this example, the task queue becomes empty when the observable emits the first quote.
The test resumes within the promise callback, which calls detectChanges()
to\nupdate the quote element with the expected text.
While the waitForAsync()
and fakeAsync()
functions greatly\nsimplify Angular asynchronous testing,\nyou can still fall back to the traditional technique\nand pass it
a function that takes a\ndone
callback.
You can't call done()
in waitForAsync()
or fakeAsync()
functions, because the done parameter
\nis undefined
.
Now you are responsible for chaining promises, handling errors, and calling done()
at the appropriate moments.
Writing test functions with done()
, is more cumbersome than waitForAsync()
and fakeAsync()
, but it is occasionally necessary when code involves the intervalTimer()
like setInterval
.
Here are two more versions of the previous test, written with done()
.\nThe first one subscribes to the Observable
exposed to the template by the component's quote
property.
The RxJS last()
operator emits the observable's last value before completing, which will be the test quote.\nThe subscribe
callback calls detectChanges()
to\nupdate the quote element with the test quote, in the same manner as the earlier tests.
In some tests, you're more interested in how an injected service method was called and what values it returned,\nthan what appears on screen.
\nA service spy, such as the qetQuote()
spy of the fake TwainService
,\ncan give you that information and make assertions about the state of the view.
The previous TwainComponent
tests simulated an asynchronous observable response\nfrom the TwainService
with the asyncData
and asyncError
utilities.
These are short, simple functions that you can write yourself.\nUnfortunately, they're too simple for many common scenarios.\nAn observable often emits multiple times, perhaps after a significant delay.\nA component may coordinate multiple observables\nwith overlapping sequences of values and errors.
\nRxJS marble testing is a great way to test observable scenarios,\nboth simple and complex.\nYou've likely seen the marble diagrams\nthat illustrate how observables work.\nMarble testing uses a similar marble language to\nspecify the observable streams and expectations in your tests.
\nThe following examples revisit two of the TwainComponent
tests\nwith marble testing.
Start by installing the jasmine-marbles
npm package.\nThen import the symbols you need.
Here's the complete test for getting a quote:
\nNotice that the Jasmine test is synchronous. There's no fakeAsync()
.\nMarble testing uses a test scheduler to simulate the passage of time\nin a synchronous test.
The beauty of marble testing is in the visual definition of the observable streams.\nThis test defines a cold observable that waits\nthree frames (---
),\nemits a value (x
), and completes (|
).\nIn the second argument you map the value marker (x
) to the emitted value (testQuote
).
The marble library constructs the corresponding observable, which the\ntest sets as the getQuote
spy's return value.
When you're ready to activate the marble observables,\nyou tell the TestScheduler
to flush its queue of prepared tasks like this.
This step serves a purpose analogous to tick() and whenStable()
in the\nearlier fakeAsync()
and waitForAsync()
examples.\nThe balance of the test is the same as those examples.
Here's the marble testing version of the getQuote()
error test.
It's still an async test, calling fakeAsync()
and tick(), because the component itself\ncalls setTimeout()
when processing errors.
Look at the marble observable definition.
\nThis is a cold observable that waits three frames and then emits an error,\nThe hash (#
) indicates the timing of the error that is specified in the third argument.\nThe second argument is null because the observable never emits a value.
A marble frame is a virtual unit of testing time.\nEach symbol (-
, x
, |
, #
) marks the passing of one frame.
A cold observable doesn't produce values until you subscribe to it.\nMost of your application observables are cold.\nAll HttpClient methods return cold observables.
\nA hot observable is already producing values before you subscribe to it.\nThe Router.events observable,\nwhich reports router activity, is a hot observable.
\nRxJS marble testing is a rich subject, beyond the scope of this guide.\nLearn about it on the web, starting with the\nofficial documentation.
\n\nA component with inputs and outputs typically appears inside the view template of a host component.\nThe host uses a property binding to set the input property and an event binding to\nlisten to events raised by the output property.
\nThe testing goal is to verify that such bindings work as expected.\nThe tests should set input values and listen for output events.
\nThe DashboardHeroComponent
is a tiny example of a component in this role.\nIt displays an individual hero provided by the DashboardComponent
.\nClicking that hero tells the DashboardComponent
that the user has selected the hero.
The DashboardHeroComponent
is embedded in the DashboardComponent
template like this:
The DashboardHeroComponent
appears in an *ngFor
repeater, which sets each component's hero
input property\nto the looping value and listens for the component's selected
event.
Here's the component's full definition:
\n\nWhile testing a component this simple has little intrinsic value, it's worth knowing how.\nYou can use one of these approaches:
\nDashboardComponent
.DashboardComponent
.A quick look at the DashboardComponent
constructor discourages the first approach:
The DashboardComponent
depends on the Angular router and the HeroService
.\nYou'd probably have to replace them both with test doubles, which is a lot of work.\nThe router seems particularly challenging.
The discussion below covers testing components that require the router.
\nThe immediate goal is to test the DashboardHeroComponent
, not the DashboardComponent
,\nso, try the second and third options.
Here's the meat of the spec file setup.
\nNote how the setup code assigns a test hero (expectedHero
) to the component's hero
property,\nemulating the way the DashboardComponent
would set it\nvia the property binding in its repeater.
The following test verifies that the hero name is propagated to the template via a binding.
\nBecause the template passes the hero name through the Angular UpperCasePipe
,\nthe test must match the element value with the upper-cased name.
This small test demonstrates how Angular tests can verify a component's visual\nrepresentation—something not possible with\ncomponent class tests—at\nlow cost and without resorting to much slower and more complicated end-to-end tests.
\nClicking the hero should raise a selected
event that\nthe host component (DashboardComponent
presumably) can hear:
The component's selected
property returns an EventEmitter
,\nwhich looks like an RxJS synchronous Observable
to consumers.\nThe test subscribes to it explicitly just as the host component does implicitly.
If the component behaves as expected, clicking the hero's element\nshould tell the component's selected
property to emit the hero
object.
The test detects that event through its subscription to selected
.
The heroDe
in the previous test is a DebugElement
that represents the hero <div>
.
It has Angular properties and methods that abstract interaction with the native element.\nThis test calls the DebugElement.triggerEventHandler
with the \"click\" event name.\nThe \"click\" event binding responds by calling DashboardHeroComponent.click()
.
The Angular DebugElement.triggerEventHandler
can raise any data-bound event by its event name.\nThe second parameter is the event object passed to the handler.
The test triggered a \"click\" event with a null
event object.
The test assumes (correctly in this case) that the runtime\nevent handler—the component's click()
method—doesn't\ncare about the event object.
Other handlers are less forgiving. For example, the RouterLink
\ndirective expects an object with a button
property\nthat identifies which mouse button (if any) was pressed during the click.\nThe RouterLink
directive throws an error if the event object is missing.
The following test alternative calls the native element's own click()
method,\nwhich is perfectly fine for this component.
Clicking a button, an anchor, or an arbitrary HTML element is a common test task.
\nMake that consistent and easy by encapsulating the click-triggering process\nin a helper such as the click()
function below:
The first parameter is the element-to-click. If you wish, you can pass a\ncustom event object as the second parameter. The default is a (partial)\nleft-button mouse event object\naccepted by many handlers including the RouterLink
directive.
The click()
helper function is not one of the Angular testing utilities.\nIt's a function defined in this guide's sample code.\nAll of the sample tests use it.\nIf you like it, add it to your own collection of helpers.
Here's the previous test, rewritten using the click helper.
\nThe previous tests played the role of the host DashboardComponent
themselves.\nBut does the DashboardHeroComponent
work correctly when properly data-bound to a host component?
You could test with the actual DashboardComponent
.\nBut doing so could require a lot of setup,\nespecially when its template features an *ngFor
repeater,\nother components, layout HTML, additional bindings,\na constructor that injects multiple services,\nand it starts interacting with those services right away.
Imagine the effort to disable these distractions, just to prove a point\nthat can be made satisfactorily with a test host like this one:
\nThis test host binds to DashboardHeroComponent
as the DashboardComponent
would\nbut without the noise of the Router
, the HeroService
, or the *ngFor
repeater.
The test host sets the component's hero
input property with its test hero.\nIt binds the component's selected
event with its onSelected
handler,\nwhich records the emitted hero in its selectedHero
property.
Later, the tests will be able to easily check selectedHero
to verify that the\nDashboardHeroComponent.selected
event emitted the expected hero.
The setup for the test-host tests is similar to the setup for the stand-alone tests:
\nThis testing module configuration shows three important differences:
\nDashboardHeroComponent
and the TestHostComponent
.TestHostComponent
instead of the DashboardHeroComponent
.TestHostComponent
sets the DashboardHeroComponent.hero
with a binding.The createComponent
returns a fixture
that holds an instance of TestHostComponent
instead of an instance of DashboardHeroComponent
.
Creating the TestHostComponent
has the side-effect of creating a DashboardHeroComponent
\nbecause the latter appears within the template of the former.\nThe query for the hero element (heroEl
) still finds it in the test DOM,\nalbeit at greater depth in the element tree than before.
The tests themselves are almost identical to the stand-alone version:
\nOnly the selected event test differs. It confirms that the selected DashboardHeroComponent
hero\nreally does find its way up through the event binding to the host component.
A routing component is a component that tells the Router
to navigate to another component.\nThe DashboardComponent
is a routing component because the user can\nnavigate to the HeroDetailComponent
by clicking on one of the hero buttons on the dashboard.
Routing is pretty complicated.\nTesting the DashboardComponent
seemed daunting in part because it involves the Router
,\nwhich it injects together with the HeroService
.
Mocking the HeroService
with a spy is a familiar story.\nBut the Router
has a complicated API and is entwined with other services and application preconditions. Might it be difficult to mock?
Fortunately, not in this case because the DashboardComponent
isn't doing much with the Router
This is often the case with routing components.\nAs a rule you test the component, not the router,\nand care only if the component navigates with the right address under the given conditions.
\nProviding a router spy for this component test suite happens to be as easy\nas providing a HeroService
spy.
The following test clicks the displayed hero and confirms that\nRouter.navigateByUrl
is called with the expected url.
A routed component is the destination of a Router
navigation.\nIt can be trickier to test, especially when the route to the component includes parameters.\nThe HeroDetailComponent
is a routed component that is the destination of such a route.
When a user clicks a Dashboard hero, the DashboardComponent
tells the Router
\nto navigate to heroes/:id
.\nThe :id
is a route parameter whose value is the id
of the hero to edit.
The Router
matches that URL to a route to the HeroDetailComponent
.\nIt creates an ActivatedRoute
object with the routing information and\ninjects it into a new instance of the HeroDetailComponent
.
Here's the HeroDetailComponent
constructor:
The HeroDetail
component needs the id
parameter so it can fetch\nthe corresponding hero via the HeroDetailService
.\nThe component has to get the id
from the ActivatedRoute.paramMap
property\nwhich is an Observable
.
It can't just reference the id
property of the ActivatedRoute.paramMap
.\nThe component has to subscribe to the ActivatedRoute.paramMap
observable and be prepared\nfor the id
to change during its lifetime.
The ActivatedRoute in action section of the Router tutorial: tour of heroes guide covers ActivatedRoute.paramMap
in more detail.
Tests can explore how the HeroDetailComponent
responds to different id
parameter values\nby manipulating the ActivatedRoute
injected into the component's constructor.
You know how to spy on the Router
and a data service.
You'll take a different approach with ActivatedRoute
because
paramMap
returns an Observable
that can emit more than one value\nduring a test.convertToParamMap()
, to create a ParamMap
.ActivatedRoute
.These differences argue for a re-usable stub class.
\nThe following ActivatedRouteStub
class serves as a test double for ActivatedRoute
.
Consider placing such helpers in a testing
folder sibling to the app
folder.\nThis sample puts ActivatedRouteStub
in testing/activated-route-stub.ts
.
Consider writing a more capable version of this stub class with\nthe marble testing library.
\nHere's a test demonstrating the component's behavior when the observed id
refers to an existing hero:
The createComponent()
method and page
object are discussed below.\nRely on your intuition for now.
When the id
cannot be found, the component should re-route to the HeroListComponent
.
The test suite setup provided the same router spy described above which spies on the router without actually navigating.
\nThis test expects the component to try to navigate to the HeroListComponent
.
While this app doesn't have a route to the HeroDetailComponent
that omits the id
parameter, it might add such a route someday.\nThe component should do something reasonable when there is no id
.
In this implementation, the component should create and display a new hero.\nNew heroes have id=0
and a blank name
. This test confirms that the component behaves as expected:
Component templates often have nested components, whose templates\nmay contain more components.
\nThe component tree can be very deep and, most of the time, the nested components\nplay no role in testing the component at the top of the tree.
\nThe AppComponent
, for example, displays a navigation bar with anchors and their RouterLink
directives.
While the AppComponent
class is empty,\nyou may want to write unit tests to confirm that the links are wired properly\nto the RouterLink
directives, perhaps for the reasons explained below.
To validate the links, you don't need the Router
to navigate and you don't\nneed the <router-outlet>
to mark where the Router
inserts routed components.
The BannerComponent
and WelcomeComponent
\n(indicated by <app-banner>
and <app-welcome>
) are also irrelevant.
Yet any test that creates the AppComponent
in the DOM will also create instances of\nthese three components and, if you let that happen,\nyou'll have to configure the TestBed
to create them.
If you neglect to declare them, the Angular compiler won't recognize the\n<app-banner>
, <app-welcome>
, and <router-outlet>
tags in the AppComponent
template\nand will throw an error.
If you declare the real components, you'll also have to declare their nested components\nand provide for all services injected in any component in the tree.
\nThat's too much effort just to answer a few simple questions about links.
\nThis section describes two techniques for minimizing the setup.\nUse them, alone or in combination, to stay focused on testing the primary component.
\n\nIn the first technique, you create and declare stub versions of the components\nand directive that play little or no role in the tests.
\nThe stub selectors match the selectors for the corresponding real components.\nBut their templates and classes are empty.
\nThen declare them in the TestBed
configuration next to the\ncomponents, directives, and pipes that need to be real.
The AppComponent
is the test subject, so of course you declare the real version.
The RouterLinkDirectiveStub
, described later, is a test version\nof the real RouterLink
that helps with the link tests.
The rest are stubs.
\n\nIn the second approach, add NO_ERRORS_SCHEMA
to the TestBed.schemas
metadata.
The NO_ERRORS_SCHEMA
tells the Angular compiler to ignore unrecognized elements and attributes.
The compiler will recognize the <app-root>
element and the routerLink
attribute\nbecause you declared a corresponding AppComponent
and RouterLinkDirectiveStub
\nin the TestBed
configuration.
But the compiler won't throw an error when it encounters <app-banner>
, <app-welcome>
, or <router-outlet>
.\nIt simply renders them as empty tags and the browser ignores them.
You no longer need the stub components.
\nThese are techniques for Shallow Component Testing ,\nso-named because they reduce the visual surface of the component to just those elements\nin the component's template that matter for tests.
\nThe NO_ERRORS_SCHEMA
approach is the easier of the two but don't overuse it.
The NO_ERRORS_SCHEMA
also prevents the compiler from telling you about the missing\ncomponents and attributes that you omitted inadvertently or misspelled.\nYou could waste hours chasing phantom bugs that the compiler would have caught in an instant.
The stub component approach has another advantage.\nWhile the stubs in this example were empty,\nyou could give them stripped-down templates and classes if your tests\nneed to interact with them in some way.
\nIn practice you will combine the two techniques in the same setup,\nas seen in this example.
\nThe Angular compiler creates the BannerComponentStub
for the <app-banner>
element\nand applies the RouterLinkStubDirective
to the anchors with the routerLink
attribute,\nbut it ignores the <app-welcome>
and <router-outlet>
tags.
The real RouterLinkDirective
is quite complicated and entangled with other components\nand directives of the RouterModule
.\nIt requires challenging setup to mock and use in tests.
The RouterLinkDirectiveStub
in this sample code replaces the real directive\nwith an alternative version designed to validate the kind of anchor tag wiring\nseen in the AppComponent
template.
The URL bound to the [routerLink]
attribute flows in to the directive's linkParams
property.
The HostListener
wires the click event of the host element\n(the <a>
anchor elements in AppComponent
) to the stub directive's onClick
method.
Clicking the anchor should trigger the onClick()
method,\nwhich sets the stub's telltale navigatedTo
property.\nTests inspect navigatedTo
to confirm that clicking the anchor\nsets the expected route definition.
Whether the router is configured properly to navigate with that route definition is a\nquestion for a separate set of tests.
\nA little more setup triggers the initial data binding and gets references to the navigation links:
\nThree points of special interest:
\nYou can locate the anchor elements with an attached directive using By.directive
.
The query returns DebugElement
wrappers around the matching elements.
Each DebugElement
exposes a dependency injector with the\nspecific instance of the directive attached to that element.
The AppComponent
links to validate are as follows:
Here are some tests that confirm those links are wired to the routerLink
directives\nas expected:
The \"click\" test in this example is misleading.\nIt tests the RouterLinkDirectiveStub
rather than the component.\nThis is a common failing of directive stubs.
It has a legitimate purpose in this guide.\nIt demonstrates how to find a RouterLink
element, click it, and inspect a result,\nwithout engaging the full router machinery.\nThis is a skill you may need to test a more sophisticated component, one that changes the display,\nre-calculates parameters, or re-arranges navigation options when the user clicks the link.
Stubbed RouterLink
tests can confirm that a component with links and an outlet is setup properly,\nthat the component has the links it should have, and that they are all pointing in the expected direction.\nThese 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.\nRelying on the real router would make them brittle.\nThey could fail for reasons unrelated to the component.\nFor example, a navigation guard could prevent an unauthorized user from visiting the HeroListComponent
.\nThat'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\nin the presence of conditions that influence guards such as whether the user is authenticated and authorized.
\nA future guide update will explain how to write such\ntests with the RouterTestingModule
.
The HeroDetailComponent
is a simple view with a title, two hero fields, and two buttons.
But there's plenty of template complexity even in this simple form.
\nTests that exercise the component need ...
\nEven a small form such as this one can produce a mess of tortured conditional setup and CSS element selection.
\nTame the complexity with a Page
class that handles access to component properties\nand encapsulates the logic that sets them.
Here is such a Page
class for the hero-detail.component.spec.ts
Now the important hooks for component manipulation and inspection are neatly organized and accessible from an instance of Page
.
A createComponent
method creates a page
object and fills in the blanks once the hero
arrives.
The HeroDetailComponent tests in an earlier section demonstrate how createComponent
and page
\nkeep the tests short and on message.\nThere are no distractions: no waiting for promises to resolve and no searching the DOM for element values to compare.
Here are a few more HeroDetailComponent
tests to reinforce the point.
You can ignore this section if you only run tests with the CLI ng test
command\nbecause the CLI compiles the application before running the tests.
If you run tests in a non-CLI environment, the tests may fail with a message like this one:
\nThe root of the problem is at least one of the components involved in the test\nspecifies an external template or CSS file as\nthe following version of the BannerComponent
does.
The test fails when the TestBed
tries to create the component.
Recall that the app hasn't been compiled.\nSo when you call createComponent()
, the TestBed
compiles implicitly.
That's not a problem when the source code is in memory.\nBut the BannerComponent
requires external files\nthat the compiler must read from the file system,\nan inherently asynchronous operation.
If the TestBed
were allowed to continue, the tests would run and fail mysteriously\nbefore the compiler could finished.
The preemptive error message tells you to compile explicitly with compileComponents()
.
You must call compileComponents()
within an asynchronous test function.
If you neglect to make the test function async\n(e.g., forget to use waitForAsync()
as described below),\nyou'll see this error message
A typical approach is to divide the setup logic into two separate beforeEach()
functions:
beforeEach()
that compiles the componentsbeforeEach()
that performs the remaining setup.To follow this pattern, import the waitForAsync()
helper with the other testing symbols.
Write the first async beforeEach
like this.
The waitForAsync()
helper function takes a parameterless function with the body of the setup.
The TestBed.configureTestingModule()
method returns the TestBed
class so you can chain\ncalls to other TestBed
static methods such as compileComponents()
.
In this example, the BannerComponent
is the only component to compile.\nOther examples configure the testing module with multiple components\nand may import application modules that hold yet more components.\nAny of them could require external files.
The TestBed.compileComponents
method asynchronously compiles all components configured in the testing module.
Do not re-configure the TestBed
after calling compileComponents()
.
Calling compileComponents()
closes the current TestBed
instance to further configuration.\nYou cannot call any more TestBed
configuration methods, not configureTestingModule()
\nnor any of the override...
methods. The TestBed
throws an error if you try.
Make compileComponents()
the last step\nbefore calling TestBed.createComponent()
.
The second, synchronous beforeEach()
contains the remaining setup steps,\nwhich include creating the component and querying for elements to inspect.
You can count on the test runner to wait for the first asynchronous beforeEach
to finish before calling the second.
You can consolidate the two beforeEach()
functions into a single, async beforeEach()
.
The compileComponents()
method returns a promise so you can perform the\nsynchronous setup tasks after compilation by moving the synchronous code\ninto a then(...)
callback.
There's no harm in calling compileComponents()
when it's not required.
The component test file generated by the CLI calls compileComponents()
\neven though it is never required when running ng test
.
The tests in this guide only call compileComponents
when necessary.
Earlier component tests configured the testing module with a few declarations
like this:
The DashboardComponent
is simple. It needs no help.\nBut more complex components often depend on other components, directives, pipes, and providers\nand these must be added to the testing module too.
Fortunately, the TestBed.configureTestingModule
parameter parallels\nthe metadata passed to the @NgModule
decorator\nwhich means you can also specify providers
and imports
.
The HeroDetailComponent
requires a lot of help despite its small size and simple construction.\nIn addition to the support it receives from the default testing module CommonModule
, it needs:
NgModel
and friends in the FormsModule
to enable two-way data binding.TitleCasePipe
from the shared
folder.One approach is to configure the testing module from the individual pieces as in this example:
\nNotice that the beforeEach()
is asynchronous and calls TestBed.compileComponents
\nbecause the HeroDetailComponent
has an external template and css file.
As explained in Calling compileComponents() above,\nthese tests could be run in a non-CLI environment\nwhere Angular would have to compile them in the browser.
\nBecause many app components need the FormsModule
and the TitleCasePipe
, the developer created\na SharedModule
to combine these and other frequently requested parts.
The test configuration can use the SharedModule
too as seen in this alternative setup:
It's a bit tighter and smaller, with fewer import statements (not shown).
\n\nThe HeroDetailComponent
is part of the HeroModule
Feature Module that aggregates more of the interdependent pieces\nincluding the SharedModule
.\nTry a test configuration that imports the HeroModule
like this one:
That's really crisp. Only the test doubles in the providers
remain. Even the HeroDetailComponent
declaration is gone.
In fact, if you try to declare it, Angular will throw an error because\nHeroDetailComponent
is declared in both the HeroModule
and the DynamicTestModule
\ncreated by the TestBed
.
Importing the component's feature module can be the easiest way to configure tests\nwhen there are many mutual dependencies within the module and\nthe module is small, as feature modules tend to be.
\nThe HeroDetailComponent
provides its own HeroDetailService
.
It's not possible to stub the component's HeroDetailService
in the providers
of the TestBed.configureTestingModule
.\nThose are providers for the testing module, not the component. They prepare the dependency injector at the fixture level.
Angular creates the component with its own injector, which is a child of the fixture injector.\nIt registers the component's providers (the HeroDetailService
in this case) with the child injector.
A test cannot get to child injector services from the fixture injector.\nAnd TestBed.configureTestingModule
can't configure them either.
Angular has been creating new instances of the real HeroDetailService
all along!
These tests could fail or timeout if the HeroDetailService
made its own XHR calls to a remote server.\nThere might not be a remote server to call.
Fortunately, the HeroDetailService
delegates responsibility for remote data access to an injected HeroService
.
The previous test configuration replaces the real HeroService
with a TestHeroService
\nthat intercepts server requests and fakes their responses.
What if you aren't so lucky. What if faking the HeroService
is hard?\nWhat if HeroDetailService
makes its own server requests?
The TestBed.overrideComponent
method can replace the component's providers
with easy-to-manage test doubles\nas seen in the following setup variation:
Notice that TestBed.configureTestingModule
no longer provides a (fake) HeroService
because it's not needed.
Focus on the overrideComponent
method.
It takes two arguments: the component type to override (HeroDetailComponent
) and an override metadata object.\nThe override metadata object is a generic defined as follows:
A metadata override object can either add-and-remove elements in metadata properties or completely reset those properties.\nThis example resets the component's providers
metadata.
The type parameter, T
, is the kind of metadata you'd pass to the @Component
decorator:
This example completely replaces the component's providers
array with a new array containing a HeroDetailServiceSpy
.
The HeroDetailServiceSpy
is a stubbed version of the real HeroDetailService
\nthat fakes all necessary features of that service.\nIt neither injects nor delegates to the lower level HeroService
\nso there's no need to provide a test double for that.
The related HeroDetailComponent
tests will assert that methods of the HeroDetailService
\nwere called by spying on the service methods.\nAccordingly, the stub implements its methods as spies:
Now the tests can control the component's hero directly by manipulating the spy-stub's testHero
\nand confirm that service methods were called.
The TestBed.overrideComponent
method can be called multiple times for the same or different components.\nThe TestBed
offers similar overrideDirective
, overrideModule
, and overridePipe
methods\nfor digging into and replacing parts of these other classes.
Explore the options and combinations on your own.
\n\n \n