Как проследить вызов службы в Angular2
Следуя примеру кода, найденному в Ari Lerner ng-book2, и используя Angular 2 beta 7, я пытаюсь ошибочно высмеивать вызов службы безубыточности.
Это основной компонент, использующий службу:
пользовательского 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);
}
);
}
}
И это сама услуга.
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());
}
}
Чтобы проверить UserListComponent
, я пытаюсь издеваться над UserService
и вызывать его вызов метода getAllUser
с помощью следующего кода:
пользовательского 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();
});
}));
});
При использовании кармы для запуска теста я получаю следующую консольную ошибку:
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
Кто-нибудь знает, почему эта ошибка возникает или надлежащий способ издеваться над шпионажем службы для модульного тестирования компонента Angular 2?
Ответы
Ответ 1
Я использую немного другой подход и использую саму инъекцию, чтобы вместо этого получить экземпляр службы через DI. Итак, ваш тест будет выглядеть так:
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]');
});
}))
);
});
Изменить для Angular 4
В последних документах есть более простой способ получить доступ к службе с помощью компонента-инжектора:
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
const mockService = fixture.debugElement.injector.get(MyService);
Изменить для 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>);
Ответ 2
Другие решения не работали для меня, поэтому я ввел службу injector
из 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);
});
})
Ответ 3
Я нашел решение
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';
User-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]');
});
}))
);
});
user.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});
}
}
Ответ 4
Для всех новичков для тестирования в angular 2 (так же, как и я здесь)
Способы createSpy или SpyOn доступны только в том случае, если у вас есть правильные идентификаторы для установки жасмина. В противном случае вы получите ошибку typescript.
проверьте свой файл typings.json, а если нет, запустите этот
typings install --save --global registry: dt/jasmine