En suivant l'exemple de code trouvé sur Ari Lerner ng-book2 et en utilisant Angular 2 beta 7, j'essaie de moquer et d'espionner un appel à un service sans succès.
Il s'agit du principal composant utilisant le 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);
}
);
}
}
Et c'est le service lui-même.
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());
}
}
Afin de tester le UserListComponent
, j'essaye de me moquer du UserService
et d'espionner son appel de méthode getAllUser
en utilisant le code suivant:
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();
});
}));
});
Lorsque j'utilise karma pour exécuter le test, j'obtiens l'erreur de console suivante:
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
Quelqu'un sait-il pourquoi cette erreur est levée ou la bonne façon de se moquer et d'espionner un service pour tester un composant Angular 2 component?
J'adopte une approche légèrement différente et j'utilise inject lui-même pour mettre la main sur l'instance de service via DI à la place. Votre test ressemblerait donc à:
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]');
});
}))
);
});
Modifier pour Angular 4
Les derniers docs ont un moyen plus simple de mettre la main sur un service en utilisant l'injecteur du composant:
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
const mockService = fixture.debugElement.injector.get(MyService);
Les autres solutions n'ont pas fonctionné pour moi, j'ai donc injecté le service avec injector
de debugElement
.
import { TestBed,
async } from '@angular/core/testing';
@injectable()
class MyService {
public method () {}
}
let MyMockedService = {
method: () => {}
}
@Component({
template: ''
})
class MyComponent {
constructor(private myService: MyService) {;}
public method () {
this.myService.method();
}
}
describe('Test', () => {
beforeEach(async(() => {
TestBed
.configureTestingModule({
imports: [
CommonModule
],
declarations: [
MyComponent
],
providers: [
{ provide: MyService, useValue: MyMockedService}
]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(MyComponent);
myComponent = fixture.componentInstance;
});
}));
it('should spy on service', () => {
let myMockedService = fixture.debugElement.injector.get(MyMockedService);
spyOn(myMockedService, 'method');
myComponent.method();
expect(myMockedService.method);
});
})
J'ai trouvé la solution
test.entry.ts
import {setBaseTestProviders} from 'angular2/testing';
import {
TEST_BROWSER_PLATFORM_PROVIDERS,
TEST_BROWSER_APPLICATION_PROVIDERS
} from 'angular2/platform/testing/browser';
setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS);
import './user-list.component.spec';
ser-list.component.spec.ts
import {
describe,
expect,
it,
tick,
inject,
fakeAsync,
TestComponentBuilder,
ComponentFixture,
beforeEachProviders
} from 'angular2/testing';
import {UserListComponent} from './user-list.component';
import {MockUserService} from './user.service.mock';
describe('When loading the UserListComponent', () => {
let mockUserService: MockUserService;
beforeEachProviders(() => {
mockUserService = new MockUserService();
return [mockUserService.getProvider()];
});
it('should call the getAllUsers method from the UserService',
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
spyOn(mockUserService, 'getAllUsers');
tcb
.createAsync(UserListComponent)
.then((fixture: ComponentFixture) => {
tick();
fixture.detectChanges();
expect(mockUserService.getAllUsers).toHaveBeenCalled();
});
}))
);
it('should show one mocked user',
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
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]');
});
}))
);
});
ser.service.mock.ts
import {provide, Provider} from 'angular2/core';
import {UserService} from './user.service';
import * as Rx from 'rxjs/Rx';
export class MockUserService {
public fakeResponse: any = null;
public getAllUsers(): Rx.Observable<any> {
let subject = new Rx.ReplaySubject()
subject.next(this.fakeResponse);
return subject;
}
public setResponse(response: any): void {
this.fakeResponse = response;
}
public getProvider(): Provider {
return provide(UserService, {useValue: this});
}
}
Pour tous les débutants de tester en angular 2 (comme moi ici)
Les méthodes createSpy ou SpyOn ne sont "disponibles" que si vous avez installé les bons types de jasmin. Sinon, cela vous générera une erreur TypeScript.
archivez votre fichier typings.json et s'il n'est pas présent, exécutez ce
typings install --save --global Registry: dt/jasmine