forked from angular/angular.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtesting.jade
1913 lines (1573 loc) · 84.9 KB
/
testing.jade
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
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)
* [Test a routed component with parameters](#routed-component-w-param)
* [Use a _page_ object to simplify setup](#page-object)
* [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 your 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 files.
This script tells SystemJS where to find those files and how to load them.
It's the same version of `systemjs.config.js` 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/`
.alert.is-important
:marked
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)
:marked
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.
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.
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
: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")
a(href="#top").to-top Back to top
.l-hr
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 test doubles for the 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`.
The `TestBed` creates an Angular testing 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()`;
:marked
Thanks to initialization in the [testing shims](#setup),
the default `TestBed` instance is pre-configured with a baseline of default providers and declarables (components, directives, and pipes)
that almost everyone needs.
The shims in this chapter are designed for testing a browser application so the default configuration includes the `CommonModule` declarables from `@angular/common`
and the `BrowserModule` providers (some of them mocked) from `@angular/platform-browser`.
This default testing module configuration is a _foundation_ for testing _any_ browser app.
You call `TestBed.configureTestingModule` with an object that defines additional imports, declarations, providers and schemas
to reshape the testing module to fit your application tests.
Optional `override...` methods can fine-tune aspects of the configuration.
After configuring the `TestBed`, tell it to create an instance of the test component and the test fixture
that 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 _Angular Testing Platform_ APIs appears [later in the chapter](#atp-api).
Let's dive right into Angular testing, starting with with the components of a sample application.
a(href="#top").to-top Back to top
.l-hr
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 up to a minute 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='.')
: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='.')
: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 testing 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_.
a(href="#top").to-top Back to top
.l-hr
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='.')
:marked
The `WelcomeComponent` has decision logic that interacts with the service;
such logic makes this component worth testing.
Here's the testing 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 testing module with a stub `UserService`.
## Provide service test doubles
A component under test doesn't have to be injected with real services.
In fact, it is usually better if they are test doubles (stubs, fakes, spies, or mocks).
The purpose of the spec is to test the component, not the service,
and real services can be trouble.
Injecting the real `UserService` could be a nightmare.
The real service might try to ask the user for login credentials and
try to reach an authentication server.
These behaviors could be hard to intercept.
It is far easier to create and register a test double in place of the real `UserService`.
This particular test suite supplies a minimal `UserService` stub that satisfies the needs of the `WelcomeComponent`
and its tests:
+makeExample('testing/ts/app/welcome.component.spec.ts', 'user-service-stub')(format='.')
a#injected-service-reference
:marked
## Referencing injected services
The tests need access to the injected (stubbed) `UserService`.
You cannot reference the `userServiceStub` object provided to the testing module.
**It does not work!**
Surprisingly, the instance actually injected into the component is _not the same_ object
as the provided `userServiceStub`.
.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 stubbed `UserService` is called and 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.
a(href="#top").to-top Back to top
.l-hr
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 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
:marked
### Spying on the real service
This setup is similar to the [`welcome.component.spec` setup](#welcome-spec-setup).
But instead of creating a stubbed service object, it injects the _real_ service (see the testing module `providers`) and
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
: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 an independent feature of the _Angular Testing Platform_.
It simplifyies coding of asynchronous tests by arranging for the tester's code to run in a special _async test zone_.
The `async` function _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 and
there is no `done` function to call as there is in standard Jasmine asynchronous tests.
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
: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 another, independent feature of the _Angular Testing Platform_.
Like [async](#), it _takes_ a parameterless function and _returns_ a parameterless function
which becomes the argument to the Jasmine `it` call.
The `fakeAsync` function enables a linear coding style by running the test body in a special _fakeAsync test zone_.
The principle advantage of `fakeAsync` over `async` is that the test appears to be synchronous.
There are no promises at all.
No `then(...)` chains to disrupt the visible flow of control.
There are limitations. For example, you cannot make an XHR call from within a `fakeAsync`.
a#tick
a#tick-first-look
:marked
## The _tick_ function
Compare the third and fourth tests. Notice that `fixture.whenStable` is gone, replaced by `tick()`.
The `tick` function is a part of the _Angular Testing Platform_ 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.
a(href="#top").to-top Back to top
.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 testing 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 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
:marked
## _compileComponents_
In this example, `Testbed.compileComponents` compiles one component, the `DashboardComponent`.
It's the only declared component in this testing 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 replace them both with test doubles and that looks like a lot of work.
The router seems particularly challenging.
.l-sub-section
:marked
The [discussion below](#routed-component) covers testing components that requre the router.
:marked
The immediate goal is to test the `DashboardHeroComponent`, not the `DashboardComponent`, and there's no need
to work hard unnecessarily. Let's try the second and third options.
## 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 raise 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 testing 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.
Stubbing the router with a test implementation is an easy option. This should do the trick:
+makeExample('testing/ts/app/dashboard/dashboard.component.spec.ts', 'router-stub', 'app/dashboard/dashboard.component.spec.ts (Router Stub)')(format='.')
:marked
Now we setup the testing module with test stubs for the `Router` and `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='.')