Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to spy a service call in Angular2

Following the code example found on Ari Lerner ng-book2, and using Angular 2 beta 7, I'm trying to mock and spy a call to a service unsuccessfully.

This is the main component using the service:

user-list.component.ts

import {Component, OnInit} from 'angular2/core'; import {UserService} from './user.service'; import {IUser} from './user.model';  @Component({   selector: 'user-list',   providers: [UserService],   template: `     <div *ngFor="#user of users" class="user">       <span class="username">Username: {{ user.username }}</span><br>       <span class="email">Email: {{ user.email }}</span>     </div>   ` }) export class UserListComponent implements OnInit {   public users: IUser[];   private userService: UserService;    constructor(userService: UserService) {     this.userService = userService;   }    ngOnInit(): void {     this.userService.getAllUsers().subscribe(       (users: IUser[]) => {         this.users = users;       },       (error: any) => {         console.log(error);       }     );   } } 

And this is the service itself.

user.service.ts

import {Injectable} from 'angular2/core'; import {Http} from 'angular2/http'; import {Observable} from 'rxjs/Observable'; import 'rxjs/Rx'; import {IUser} from './user.model';  @Injectable() export class UserService {   private http: Http;   private baseUrl: string = 'http://jsonplaceholder.typicode.com/users';    constructor(http: Http) {     this.http = http;   }    public getAllUsers(): Observable<IUser[]> {     return this.http.get(this.baseUrl)       .map(res => res.json());   } } 

In order to test the UserListComponent, I'm trying to mock the UserService and spy its method call getAllUser using the following code:

user-list.component.spec.ts

import {   describe,    expect,    it,   injectAsync,   TestComponentBuilder,   ComponentFixture,   setBaseTestProviders, } from 'angular2/testing';  import {SpyObject} from 'angular2/testing_internal';  import {   TEST_BROWSER_PLATFORM_PROVIDERS,    TEST_BROWSER_APPLICATION_PROVIDERS } from 'angular2/platform/testing/browser';  import {provide} from 'angular2/core';  import {UserListComponent} from './user-list.component'; import {UserService} from './user.service';  class SpyUserService extends SpyObject {   public getAllUsers: Function;   public fakeResponse: any = null;    constructor() {     super(UserService);     this.getAllUsers = this.spy('getAllUsers').andReturn(this);   }    public subscribe(callback) {     callback(this.fakeResponse);   }    public setResponse(data: any): void {     this.fakeResponse = data;   } }  describe('When rendering the UserListComponent and mocking the UserService', () => {    setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS);      it('should show one mocked user', injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {      let spyUserService = new SpyUserService();     spyUserService.setResponse([{       username: 'ryan',       email: '[email protected]'     }]);      return tcb       .overrideProviders(UserListComponent, [provide(UserService, {useValue: spyUserService})])       .createAsync(UserListComponent)       .then((fixture: ComponentFixture) => {         fixture.detectChanges();         expect(spyUserService.getAllUsers).toHaveBeenCalled();       });   }));  }); 

When using karma to run the test I'm getting the following console error:

Chrome 48.0.2564 (Mac OS X 10.11.3) ERROR   Uncaught TypeError: Cannot read property 'isSlow' of null   at /Users/david/apps/sandbox/angular2-testing-cookbook/src/tests.entry.ts:19430 

Do anybody know why is this error being thrown or the proper way to mock and spy a service for unit testing an Angular 2 component?

like image 690
David Barreto Avatar asked Mar 01 '16 21:03

David Barreto


People also ask

How do you mock a service to inject in a unit test?

var service = new MockLoginService(); beforeEachProviders(() => [ provide(TestService, { useValue: service })]); it('should load languages', inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { tcb . createAsync(LoginComponent). then((fixture) => { let element = fixture.

What is Spy in unit testing?

This is what is mostly used during unit testing. When spying, you take an existing object and "replace" only some methods. This is useful when you have a huge class and only want to mock certain methods (partial mocking).

What is TestBed inject?

TestBed. configureTestingModule() helps you configure the providers. Configuring the providers means you are letting the Angular dependency injection system know about this dependency which later it can inject in to components when requested through a dependency injection token.

What is Spy in angular testing?

Spy is a feature in Jasmine that allows you to spy on something to achieve the following goals: Monitor if a function is called along with the parameters pass to it. Override function return values or properties to simulate desired situations during tests. Override the implementation of functions completely.


1 Answers

I take a slightly different approach and use inject itself to get hold of the service instance through DI instead. So your test would look like:

import {   describe,    expect,    it,   tick,   inject,   fakeAsync,   TestComponentBuilder,   ComponentFixture,   addProviders } from 'angular2/testing';  import { Component, provide } from '@angular/core';  import {UserListComponent} from './user-list.component';  import {UserService} from './user.service'; import {MockUserService} from './user.service.mock';  describe('When loading the UserListComponent', () => {    beforeEach(() => addProviders([       {provide: UserService, useClass: MockUserService}   ]));    it('should call the getAllUsers method from the UserService',      inject([TestComponentBuilder, UserService], fakeAsync((tcb: TestComponentBuilder, mockUserService: UserService) => {       spyOn(mockUserService, 'getAllUsers');        tcb         .createAsync(UserListComponent)         .then((fixture: ComponentFixture) => {           tick();           fixture.detectChanges();           expect(mockUserService.getAllUsers).toHaveBeenCalled();         });     }))   );    it('should show one mocked user',      inject([TestComponentBuilder, UserService], fakeAsync((tcb: TestComponentBuilder, mockUserService: UserService) => {       mockUserService.setResponse([{         username: 'ryan',         email: '[email protected]'       }]);        tcb         .createAsync(UserListComponent)         .then((fixture: ComponentFixture) => {           tick();           fixture.detectChanges();           let compiled = fixture.debugElement.nativeElement;           expect(compiled.querySelector('div:nth-child(1) .username')).toHaveText('Username: ryan');           expect(compiled.querySelector('div:nth-child(1) .email')).toHaveText('Email: [email protected]');         });     }))   );  }); 

Edit for Angular 4

The latest docs have a simpler way of getting hold of a service using the component's injector:

  fixture = TestBed.createComponent(TestComponent);   component = fixture.componentInstance;   const mockService = fixture.debugElement.injector.get(MyService); 

Edit for Angular 5+

  import { Component, Injector, Type } from '@angular/core';   ...    fixture = TestBed.createComponent(TestComponent);   component = fixture.componentInstance;   const mockService = fixture.debugElement.injector.get<MyService>(MyService as Type<MyService>); 
like image 170
JayChase Avatar answered Oct 03 '22 02:10

JayChase