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?
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.
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).
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.
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.
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>);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With