docs(testing): make the StubHeroDetailService a spy-stub (#2935)
This commit is contained in:
parent
499d4b3c88
commit
cd80df8dc7
|
@ -34,19 +34,23 @@ describe('HeroDetailComponent', () => {
|
|||
|
||||
////////////////////
|
||||
function overrideSetup() {
|
||||
// #docregion stub-hds
|
||||
class StubHeroDetailService {
|
||||
// #docregion hds-spy
|
||||
class HeroDetailServiceSpy {
|
||||
testHero = new Hero(42, 'Test Hero');
|
||||
|
||||
getHero(id: number | string): Promise<Hero> {
|
||||
return Promise.resolve(true).then(() => Object.assign({}, this.testHero) );
|
||||
}
|
||||
getHero = jasmine.createSpy('getHero').and.callFake(
|
||||
() => Promise
|
||||
.resolve(true)
|
||||
.then(() => Object.assign({}, this.testHero))
|
||||
);
|
||||
|
||||
saveHero(hero: Hero): Promise<Hero> {
|
||||
return Promise.resolve(true).then(() => Object.assign(this.testHero, hero) );
|
||||
}
|
||||
saveHero = jasmine.createSpy('saveHero').and.callFake(
|
||||
(hero: Hero) => Promise
|
||||
.resolve(true)
|
||||
.then(() => Object.assign(this.testHero, hero))
|
||||
);
|
||||
}
|
||||
// #enddocregion stub-hds
|
||||
// #enddocregion hds-spy
|
||||
|
||||
// the `id` value is irrelevant because ignored by service stub
|
||||
beforeEach(() => activatedRoute.testParams = { id: 99999 } );
|
||||
|
@ -70,7 +74,7 @@ function overrideSetup() {
|
|||
.overrideComponent(HeroDetailComponent, {
|
||||
set: {
|
||||
providers: [
|
||||
{ provide: HeroDetailService, useClass: StubHeroDetailService }
|
||||
{ provide: HeroDetailService, useClass: HeroDetailServiceSpy }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
@ -81,31 +85,37 @@ function overrideSetup() {
|
|||
// #enddocregion setup-override
|
||||
|
||||
// #docregion override-tests
|
||||
let hds: StubHeroDetailService;
|
||||
let hdsSpy: HeroDetailServiceSpy;
|
||||
|
||||
beforeEach( async(() => {
|
||||
createComponent();
|
||||
// get the component's injected StubHeroDetailService
|
||||
hds = fixture.debugElement.injector.get(HeroDetailService);
|
||||
// get the component's injected HeroDetailServiceSpy
|
||||
hdsSpy = fixture.debugElement.injector.get(HeroDetailService);
|
||||
}));
|
||||
|
||||
it('should have called `getHero`', () => {
|
||||
expect(hdsSpy.getHero.calls.count()).toBe(1, 'getHero called once');
|
||||
});
|
||||
|
||||
it('should display stub hero\'s name', () => {
|
||||
expect(page.nameDisplay.textContent).toBe(hds.testHero.name);
|
||||
expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name);
|
||||
});
|
||||
|
||||
it('should save stub hero change', fakeAsync(() => {
|
||||
const origName = hds.testHero.name;
|
||||
const origName = hdsSpy.testHero.name;
|
||||
const newName = 'New Name';
|
||||
|
||||
page.nameInput.value = newName;
|
||||
page.nameInput.dispatchEvent(newEvent('input')); // tell Angular
|
||||
|
||||
expect(comp.hero.name).toBe(newName, 'component hero has new name');
|
||||
expect(hds.testHero.name).toBe(origName, 'service hero unchanged before save');
|
||||
expect(hdsSpy.testHero.name).toBe(origName, 'service hero unchanged before save');
|
||||
|
||||
click(page.saveBtn);
|
||||
expect(hdsSpy.saveHero.calls.count()).toBe(1, 'saveHero called once');
|
||||
|
||||
tick(); // wait for async save to complete
|
||||
expect(hds.testHero.name).toBe(newName, 'service hero has new name after save');
|
||||
expect(hdsSpy.testHero.name).toBe(newName, 'service hero has new name after save');
|
||||
expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called');
|
||||
}));
|
||||
// #enddocregion override-tests
|
||||
|
@ -114,7 +124,7 @@ function overrideSetup() {
|
|||
inject([HeroDetailService], (service: HeroDetailService) => {
|
||||
|
||||
expect(service).toEqual({}, 'service injected from fixture');
|
||||
expect(hds).toBeTruthy('service injected into component');
|
||||
expect(hdsSpy).toBeTruthy('service injected into component');
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -164,8 +174,13 @@ function heroModuleSetup() {
|
|||
});
|
||||
|
||||
it('should save when click save but not navigate immediately', () => {
|
||||
// Get service injected into component and spy on its`saveHero` method.
|
||||
// It delegates to fake `HeroService.updateHero` which delivers a safe test result.
|
||||
const hds = fixture.debugElement.injector.get(HeroDetailService);
|
||||
const saveSpy = spyOn(hds, 'saveHero').and.callThrough();
|
||||
|
||||
click(page.saveBtn);
|
||||
expect(page.saveSpy.calls.any()).toBe(true, 'HeroDetailService.save called');
|
||||
expect(saveSpy.calls.any()).toBe(true, 'HeroDetailService.save called');
|
||||
expect(page.navSpy.calls.any()).toBe(false, 'router.navigate not called');
|
||||
});
|
||||
|
||||
|
@ -322,7 +337,6 @@ function createComponent() {
|
|||
class Page {
|
||||
gotoSpy: jasmine.Spy;
|
||||
navSpy: jasmine.Spy;
|
||||
saveSpy: jasmine.Spy;
|
||||
|
||||
saveBtn: DebugElement;
|
||||
cancelBtn: DebugElement;
|
||||
|
@ -330,14 +344,9 @@ class Page {
|
|||
nameInput: HTMLInputElement;
|
||||
|
||||
constructor() {
|
||||
// Use component's injector to see the services it injected.
|
||||
const compInjector = fixture.debugElement.injector;
|
||||
const hds = compInjector.get(HeroDetailService);
|
||||
const router = compInjector.get(Router);
|
||||
|
||||
this.gotoSpy = spyOn(comp, 'gotoList').and.callThrough();
|
||||
this.navSpy = spyOn(router, 'navigate');
|
||||
this.saveSpy = spyOn(hds, 'saveHero').and.callThrough();
|
||||
const router = TestBed.get(Router); // get router from root injector
|
||||
this.gotoSpy = spyOn(comp, 'gotoList').and.callThrough();
|
||||
this.navSpy = spyOn(router, 'navigate');
|
||||
}
|
||||
|
||||
/** Add page elements after hero arrives */
|
||||
|
|
|
@ -55,8 +55,10 @@ a#top
|
|||
- [Testing with the _Observable_ test double](#tests-w-observable-double)
|
||||
* [Use a _page_ object to simplify setup](#page-object)
|
||||
* [Setup with module imports](#import-module)
|
||||
* [Override component providers](#component-override)
|
||||
<br><br>
|
||||
<br><br>
|
||||
* [Override a component's providers](#component-override)
|
||||
- [_overrideComponent_](#override-component-method)
|
||||
- [Provide a _spy-stub_](#spy-stub)
|
||||
* [Test a _RouterOutlet_ component](#router-outlet-component)
|
||||
- [stubbing unneeded components](#stub-component)
|
||||
- [Stubbing the _RouterLink_](#router-link-stub)
|
||||
|
@ -664,7 +666,7 @@ a#testbed-get
|
|||
:marked
|
||||
The [`inject`](#inject) utility function is another way to get one or more services from the test root injector.
|
||||
|
||||
See the section "[_Override Component Providers_](#component-override)" for a use case
|
||||
See the section "[_Override a component's providers_](#component-override)" for a use case
|
||||
in which `inject` and `TestBed.get` do not work and you must get the service from the component's injector.
|
||||
:marked
|
||||
### Always get the service from an injector
|
||||
|
@ -734,7 +736,12 @@ a#service-spy
|
|||
.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.
|
||||
Pick the one that seems easiest for the current test suite.
|
||||
Don't be afraid to change your mind.
|
||||
|
||||
Spying on the real service isn't always easy, especially when the real service has injected dependencies.
|
||||
You can _stub and spy_ at the same time, as shown in [an example below](#spy-stub).
|
||||
|
||||
: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)')
|
||||
|
@ -1196,7 +1203,7 @@ figure.image-display
|
|||
* to wait until a `hero` arrives before `*ngIf` allows any element in DOM
|
||||
* element references for the title name span and name input-box to inspect their values
|
||||
* two button references to click
|
||||
* spies on services and component methods
|
||||
* spies on component and router methods
|
||||
|
||||
Even a small form such as this one can produce a mess of tortured conditional setup and CSS element selection.
|
||||
|
||||
|
@ -1309,7 +1316,7 @@ a#component-override
|
|||
as seen in the following setup variation:
|
||||
+makeExample('testing/ts/app/hero/hero-detail.component.spec.ts', 'setup-override', 'app/hero/hero-detail.component.spec.ts (Override setup)')(format='.')
|
||||
:marked
|
||||
Notice that `TestBed.configureTestingModule` no longer provides a (fake) `HeroService` because it's [not needed](#stub-hero-detail-service).
|
||||
Notice that `TestBed.configureTestingModule` no longer provides a (fake) `HeroService` because it's [not needed](#spy-stub).
|
||||
|
||||
a#override-component-method
|
||||
:marked
|
||||
|
@ -1339,17 +1346,26 @@ code-example(format="." language="javascript").
|
|||
providers?: any[];
|
||||
...
|
||||
|
||||
a#stub-hero-detail-service
|
||||
a#spy-stub
|
||||
:marked
|
||||
### _StubHeroDetailService_
|
||||
### Provide a _spy stub_ (_HeroDetailServiceSpy_)
|
||||
|
||||
This example completely replaces the component's `providers` with an array containing the `StubHeroDetailService`.
|
||||
The `StubHeroDetailService` is dead simple. It doesn't need a `HeroService` (fake or otherwise).
|
||||
+makeExample('testing/ts/app/hero/hero-detail.component.spec.ts', 'stub-hds', 'app/hero/hero-detail.component.spec.ts (StubHeroDetailService)')(format='.')
|
||||
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`
|
||||
that fakes all necessary features of that service.
|
||||
It neither injects nor delegates to the lower level `HeroService`
|
||||
so there's no need to provide a test double for that.
|
||||
|
||||
The related `HeroDetailComponent` tests will assert that methods of the `HeroDetailService`
|
||||
were called by spying on the service methods.
|
||||
Accordingly, the stub implements its methods as spies:
|
||||
+makeExample('testing/ts/app/hero/hero-detail.component.spec.ts', 'hds-spy', 'app/hero/hero-detail.component.spec.ts (HeroDetailServiceSpy)')(format='.')
|
||||
:marked
|
||||
### The override tests
|
||||
|
||||
Now the tests can control the component's hero directly by manipulating the stub's `testHero`.
|
||||
Now the tests can control the component's hero directly by manipulating the spy-stub's `testHero`
|
||||
and confirm that service methods were called.
|
||||
+makeExample('testing/ts/app/hero/hero-detail.component.spec.ts', 'override-tests', 'app/hero/hero-detail.component.spec.ts (override tests)')(format='.')
|
||||
:marked
|
||||
### More overrides
|
||||
|
|
Loading…
Reference in New Issue