Книга: Angular 2 Cookbook
Назад: Unit testing a synchronous service
Дальше: Unit testing a component with a service dependency using spies

Unit testing a component with a service dependency using stubs

Standalone component testing is easy, but you will rarely need to write meaningful tests for a component that exists in isolation. More often than not, the component will have one or many dependencies, and writing good unit tests is the difference between delight and despair.

Note

The code, links, and a live example related to this recipe are available at .

Getting ready

Suppose you already have the service from the Unit testing a synchronous service recipe. In addition, you have a component, which makes use of this service:

[src/app/magic-eight-ball/magic-eight-ball.component.ts]      import {Component} from '@angular/core';   import {MagicEightBallService} from      '../magic-eight-ball.service';      @Component({     selector: 'app-magic-eight-ball',     template: `       <button (click)="update()">Click me!</button>       <h1>{{ result }}</h1>     `   })   export class MagicEightBallComponent {     result: string = '';        constructor(private magicEightBallService_: MagicEightBallService) {}        update() {       this.result = this.magicEightBallService_.reveal();     }   }   

Your objective is to write a suite of unit tests for this component without setting an explicit dependency on the service.

How to do it...

Begin with a skeleton of your test file:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]      import {TestBed, async} from '@angular/core/testing';   import {MagicEightBallComponent} from      './magic-eight-ball.component';   import {MagicEightBallService} from      '../magic-eight-ball.service';      describe('Component: MagicEightBall', () => {     beforeEach(async(() => {     }));        afterEach(() => {     });        it('should begin with no text', async(() => {     }));        it('should show text after click', async(() => {     }));   });   

You'll first want to configure the test module so that it properly provides these imported targets in the test:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]      import {TestBed, async} from '@angular/core/testing';   import {MagicEightBallComponent} from      './magic-eight-ball.component';   import {MagicEightBallService} from      '../magic-eight-ball.service';      describe('Component: MagicEightBall', () => {     let fixture;        beforeEach(async(() => {       TestBed.configureTestingModule({         declarations: [           MagicEightBallComponent         ],         providers: [           MagicEightBallService         ]       });       fixture = TestBed.createComponent(MagicEightBallComponent);     }));        afterEach(() => {       fixture = undefined;     });        it('should begin with no text', async(() => {     }));        it('should show text after click', async(() => {     }));   });   

Stubbing a service dependency

Injecting the actual service works just fine, but this isn't what you want to do. You don't want to actually inject an instance of MagicEightBallService into the component, as that would set a dependency on the service and make the unit test more complicated than it needs to be. However, MagicEightBallComponent needs to import something that resembles a MagicEightBallService. An excellent solution here is to create a service stub and inject it in its place:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]      import {TestBed, async} from '@angular/core/testing';   import {MagicEightBallComponent} from      './magic-eight-ball.component';   import {MagicEightBallService} from      '../magic-eight-ball.service';      describe('Component: MagicEightBall', () => {     let fixture;     let magicEightBallResponse = 'Answer unclear';     let magicEightBallServiceStub = {       reveal: () => magicEightBallResponse     };        beforeEach(async(() => {       TestBed.configureTestingModule({         declarations: [           MagicEightBallComponent         ],         providers: [           {              provide: MagicEightBallService,              useValue: magicEightBallServiceStub            }         ]       });       fixture = TestBed.createComponent(MagicEightBallComponent);     }));        afterEach(() => {       fixture = undefined;     });        it('should begin with no text', async(() => {     }));        it('should show text after click', async(() => {     }));   });   

A component can't tell the difference between the actual service and its mock, so it will behave normally in the test conditions you've set up.

Next, you should write the preclick test by checking that the fixture's nativeElement contains no text:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]      import {TestBed, async} from '@angular/core/testing';   import {MagicEightBallComponent} from      './magic-eight-ball.component';   import {MagicEightBallService} from      '../magic-eight-ball.service';      describe('Component: MagicEightBall', () => {     let fixture;      let getHeaderEl = () =>        fixture.nativeElement.querySelector('h1');     let magicEightBallResponse = 'Answer unclear';     let magicEightBallServiceStub = {       reveal: () => magicEightBallResponse     };        beforeEach(async(() => {       TestBed.configureTestingModule({         declarations: [           MagicEightBallComponent         ],         providers: [           {              provide: MagicEightBallService,              useValue: magicEightBallServiceStub            }         ]       });       fixture = TestBed.createComponent(MagicEightBallComponent);     }));        afterEach(() => {       fixture = undefined;     });        it('should begin with no text', async(() => {       fixture.detectChanges();       expect(getHeaderEl().textContent).toEqual('');     }));        it('should show text after click', async(() => {     }));   });   

Triggering events inside the component fixture

For the second test, you should trigger a click on the button, instruct the fixture to perform change detection, and then inspect the DOM to see that the text was properly inserted. Since you have defined the text that the stub will return, you can just compare it directly with that:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]      import {TestBed, async} from '@angular/core/testing';   import {MagicEightBallComponent} from      './magic-eight-ball.component';   import {MagicEightBallService} from      '../magic-eight-ball.service';    import {By} from '@angular/platform-browser';      describe('Component: MagicEightBall', () => {     let fixture;      let getHeaderEl = () =>        fixture.nativeElement.querySelector('h1');     let magicEightBallResponse = 'Answer unclear';     let magicEightBallServiceStub = {       reveal: () => magicEightBallResponse     };        ...        it('should begin with no text', async(() => {       expect(getHeaderEl().textContent).toEqual('');     }));        it('should show text after click', async(() => {     fixture.debugElement.query(By.css('button'))       .triggerEventHandler('click', null);     fixture.detectChanges();     expect(getHeaderEl().textContent)       .toEqual(magicEightBallResponse);     }));   });   

You'll note that this needs to import and use the By.css predicate, which is required to perform DebugElement inspections.

How it works...

As demonstrated in the dependency injection chapter, providing a stub to the component is no different than providing a regular value to the core application.

The stub here is a single function that returns a static value. There is no concept of randomly selecting from the service's array of strings, and there doesn't need to be. The unit tests for the service itself ensure that it is behaving properly. Instead, the only value provided by the service here is the information it passes back to the component for interpolation back into the template.

See also

  • Writing a minimum viable unit test suite for a simple component shows you a basic example of unit testing Angular 2 components
  • Unit testing a synchronous service demonstrates how injection is mocked in unit tests
  • Unit testing a component with a service dependency using spies shows how you can keep track of service method invocations inside a unit test
Назад: Unit testing a synchronous service
Дальше: Unit testing a component with a service dependency using spies

thank you
Flame
cant read the code since it is all on a single line. Also this comments section is russian
Rakuneque
DATA COLLECTION AND ANALYSIS Two reviewers extracted data and assessed methodological quality independently lasix torsemide conversion Many others were in that space already