使用 HTTP 请求测试 Angular 2.0.0 组件
Test Angular 2.0.0 component with HTTP request
我有以下 Angular 2.0.0 组件:
import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.css']
})
export class BookListComponent implements OnInit {
books: any;
constructor(private http: Http) { }
ngOnInit() {
this.http.get('/api/books.json')
.subscribe(response => this.books = response.json());
}
}
我将如何测试 ngOnInit()
函数?
我不想包括我到目前为止尝试过的测试,因为我怀疑我偏离了正确的轨道,我不想让答案有偏见。
要模拟 Http,您需要使用 TestBed
中的 Http
提供程序配置 MockBackend
。然后你可以订阅它的连接并提供模拟响应
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
},
deps: [MockBackend, BaseRequestOptions]
},
MockBackend,
BaseRequestOptions
]
});
});
How would I test the ngOnInit() function?
问题是 Http.get
调用的异步性质。 ngOnInit
将在您调用 fixture.detectChanges()
时被调用,但是 Http 的异步性质导致测试在 Http 完成之前 运行。为此,我们使用 fakeAsync
,如前所述 ,但下一个问题是您不能使用 fakeAsync
和 templateUrl
。你可以用 setTimeout
破解它并在那里测试。那行得通。不过我个人不喜欢。
it('', async(() => {
setTimeout(() => {
// expectations here
}, 100);
})
就我个人而言,我认为您首先就存在设计缺陷。 Http
调用应抽象为服务,组件应与服务交互,而不是直接与 Http
交互。如果您将设计更改为此(我会推荐),那么您可以测试服务 , and for the component testing, create a synchronous mock, as mentioned in .
下面是 我 如何做到的完整示例
import { Component, OnInit, OnDestroy, DebugElement, Injectable } from '@angular/core';
import { CommonModule } from '@angular/common';
import { By } from '@angular/platform-browser';
import { Http, BaseRequestOptions, Response, ResponseOptions } from '@angular/http';
import { MockBackend, MockConnection } from '@angular/http/testing';
import { async, fakeAsync, inject, TestBed } from '@angular/core/testing';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
@Injectable()
class BooksService {
constructor(private http: Http) {}
getBooks(): Observable<string[]> {
return this.http.get('')
.map(res => res.json() as string[]);
}
}
class MockBooksService {
subscription: Subscription;
content;
error;
constructor() {
this.subscription = new Subscription();
spyOn(this.subscription, 'unsubscribe');
}
getBooks() {
return this;
}
subscribe(next, error) {
if (this.content && next && !error) {
next(this.content);
}
if (this.error) {
error(this.error);
}
return this.subscription;
}
}
@Component({
template: `
<h4 *ngFor="let book of books">{{ book }}</h4>
`
})
class TestComponent implements OnInit, OnDestroy {
books: string[];
subscription: Subscription;
constructor(private service: BooksService) {}
ngOnInit() {
this.subscription = this.service.getBooks().subscribe(books => {
this.books = books;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
describe('component: TestComponent', () => {
let mockService: MockBooksService;
beforeEach(() => {
mockService = new MockBooksService();
TestBed.configureTestingModule({
imports: [ CommonModule ],
declarations: [ TestComponent ],
providers: [
{ provide: BooksService, useValue: mockService }
]
});
});
it('should set the books', () => {
mockService.content = ['Book1', 'Book2'];
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
let debugEls: DebugElement[] = fixture.debugElement.queryAll(By.css('h4'));
expect(debugEls[0].nativeElement.innerHTML).toEqual('Book1');
expect(debugEls[1].nativeElement.innerHTML).toEqual('Book2');
});
it('should unsubscribe when destroyed', () => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
fixture.destroy();
expect(mockService.subscription.unsubscribe).toHaveBeenCalled();
});
});
describe('service: BooksService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
},
deps: [MockBackend, BaseRequestOptions]
},
MockBackend,
BaseRequestOptions,
BooksService
]
});
});
it('should return mocked content',
async(inject([MockBackend, BooksService],
(backend: MockBackend, service: BooksService) => {
backend.connections.subscribe((conn: MockConnection) => {
let ops = new ResponseOptions({body: '["Book1", "Book2"]'});
conn.mockRespond(new Response(ops));
});
service.getBooks().subscribe(books => {
expect(books[0]).toEqual('Book1');
expect(books[1]).toEqual('Book2');
});
})));
});
实际上我最终做了一些不同的事情。
根据 peeskillet 的建议,我重构了我的代码以使用服务。这是服务的样子。
// src/app/book.service.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
@Injectable()
export class BookService {
constructor(private http: Http) { }
getList() {
return this.http.get('/api/books.json');
}
}
这里是使用该服务的修改后的 BookListComponent
。
// book-list.component.ts
import { Component, OnInit } from '@angular/core';
import { BookService } from '../book.service';
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.css'],
providers: [BookService]
})
export class BookListComponent implements OnInit {
books: any;
constructor(private bookService: BookService) { }
ngOnInit() {
this.bookService.getList()
.subscribe(response => this.books = response.json());
}
}
最后,这是工作测试。
// book-list.component.spec.ts
/* tslint:disable:no-unused-variable */
import { TestBed, async } from '@angular/core/testing';
import { MockBackend } from '@angular/http/testing';
import { Observable } from 'rxjs/Observable';
import {
Http,
Response,
ResponseOptions,
BaseRequestOptions,
ConnectionBackend
} from '@angular/http';
import { BookListComponent } from './book-list.component';
import { BookService } from '../book.service';
describe('Component: BookList', () => {
let fixture;
let component;
let bookService;
let spy;
let testList;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
},
BookService
],
declarations: [BookListComponent]
});
fixture = TestBed.createComponent(BookListComponent);
component = fixture.debugElement.componentInstance;
bookService = fixture.debugElement.injector.get(BookService);
let observable: Observable<Response> = Observable.create(observer => {
let responseOptions = new ResponseOptions({
body: '[{ "name": "Whiteboard Interviews" }]'
});
observer.next(new Response(responseOptions));
});
spy = spyOn(bookService, 'getList')
.and.returnValue(observable);
});
it('should create an instance', () => {
expect(component).toBeTruthy();
});
it('should return a response', () => {
fixture.detectChanges();
expect(component.books).toEqual([
{ 'name': 'Whiteboard Interviews' }
]);
});
});
我有以下 Angular 2.0.0 组件:
import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.css']
})
export class BookListComponent implements OnInit {
books: any;
constructor(private http: Http) { }
ngOnInit() {
this.http.get('/api/books.json')
.subscribe(response => this.books = response.json());
}
}
我将如何测试 ngOnInit()
函数?
我不想包括我到目前为止尝试过的测试,因为我怀疑我偏离了正确的轨道,我不想让答案有偏见。
要模拟 Http,您需要使用 TestBed
中的 Http
提供程序配置 MockBackend
。然后你可以订阅它的连接并提供模拟响应
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
},
deps: [MockBackend, BaseRequestOptions]
},
MockBackend,
BaseRequestOptions
]
});
});
How would I test the ngOnInit() function?
问题是 Http.get
调用的异步性质。 ngOnInit
将在您调用 fixture.detectChanges()
时被调用,但是 Http 的异步性质导致测试在 Http 完成之前 运行。为此,我们使用 fakeAsync
,如前所述 fakeAsync
和 templateUrl
。你可以用 setTimeout
破解它并在那里测试。那行得通。不过我个人不喜欢。
it('', async(() => {
setTimeout(() => {
// expectations here
}, 100);
})
就我个人而言,我认为您首先就存在设计缺陷。 Http
调用应抽象为服务,组件应与服务交互,而不是直接与 Http
交互。如果您将设计更改为此(我会推荐),那么您可以测试服务
下面是 我 如何做到的完整示例
import { Component, OnInit, OnDestroy, DebugElement, Injectable } from '@angular/core';
import { CommonModule } from '@angular/common';
import { By } from '@angular/platform-browser';
import { Http, BaseRequestOptions, Response, ResponseOptions } from '@angular/http';
import { MockBackend, MockConnection } from '@angular/http/testing';
import { async, fakeAsync, inject, TestBed } from '@angular/core/testing';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
@Injectable()
class BooksService {
constructor(private http: Http) {}
getBooks(): Observable<string[]> {
return this.http.get('')
.map(res => res.json() as string[]);
}
}
class MockBooksService {
subscription: Subscription;
content;
error;
constructor() {
this.subscription = new Subscription();
spyOn(this.subscription, 'unsubscribe');
}
getBooks() {
return this;
}
subscribe(next, error) {
if (this.content && next && !error) {
next(this.content);
}
if (this.error) {
error(this.error);
}
return this.subscription;
}
}
@Component({
template: `
<h4 *ngFor="let book of books">{{ book }}</h4>
`
})
class TestComponent implements OnInit, OnDestroy {
books: string[];
subscription: Subscription;
constructor(private service: BooksService) {}
ngOnInit() {
this.subscription = this.service.getBooks().subscribe(books => {
this.books = books;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
describe('component: TestComponent', () => {
let mockService: MockBooksService;
beforeEach(() => {
mockService = new MockBooksService();
TestBed.configureTestingModule({
imports: [ CommonModule ],
declarations: [ TestComponent ],
providers: [
{ provide: BooksService, useValue: mockService }
]
});
});
it('should set the books', () => {
mockService.content = ['Book1', 'Book2'];
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
let debugEls: DebugElement[] = fixture.debugElement.queryAll(By.css('h4'));
expect(debugEls[0].nativeElement.innerHTML).toEqual('Book1');
expect(debugEls[1].nativeElement.innerHTML).toEqual('Book2');
});
it('should unsubscribe when destroyed', () => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
fixture.destroy();
expect(mockService.subscription.unsubscribe).toHaveBeenCalled();
});
});
describe('service: BooksService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
},
deps: [MockBackend, BaseRequestOptions]
},
MockBackend,
BaseRequestOptions,
BooksService
]
});
});
it('should return mocked content',
async(inject([MockBackend, BooksService],
(backend: MockBackend, service: BooksService) => {
backend.connections.subscribe((conn: MockConnection) => {
let ops = new ResponseOptions({body: '["Book1", "Book2"]'});
conn.mockRespond(new Response(ops));
});
service.getBooks().subscribe(books => {
expect(books[0]).toEqual('Book1');
expect(books[1]).toEqual('Book2');
});
})));
});
实际上我最终做了一些不同的事情。
根据 peeskillet 的建议,我重构了我的代码以使用服务。这是服务的样子。
// src/app/book.service.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
@Injectable()
export class BookService {
constructor(private http: Http) { }
getList() {
return this.http.get('/api/books.json');
}
}
这里是使用该服务的修改后的 BookListComponent
。
// book-list.component.ts
import { Component, OnInit } from '@angular/core';
import { BookService } from '../book.service';
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.css'],
providers: [BookService]
})
export class BookListComponent implements OnInit {
books: any;
constructor(private bookService: BookService) { }
ngOnInit() {
this.bookService.getList()
.subscribe(response => this.books = response.json());
}
}
最后,这是工作测试。
// book-list.component.spec.ts
/* tslint:disable:no-unused-variable */
import { TestBed, async } from '@angular/core/testing';
import { MockBackend } from '@angular/http/testing';
import { Observable } from 'rxjs/Observable';
import {
Http,
Response,
ResponseOptions,
BaseRequestOptions,
ConnectionBackend
} from '@angular/http';
import { BookListComponent } from './book-list.component';
import { BookService } from '../book.service';
describe('Component: BookList', () => {
let fixture;
let component;
let bookService;
let spy;
let testList;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
},
BookService
],
declarations: [BookListComponent]
});
fixture = TestBed.createComponent(BookListComponent);
component = fixture.debugElement.componentInstance;
bookService = fixture.debugElement.injector.get(BookService);
let observable: Observable<Response> = Observable.create(observer => {
let responseOptions = new ResponseOptions({
body: '[{ "name": "Whiteboard Interviews" }]'
});
observer.next(new Response(responseOptions));
});
spy = spyOn(bookService, 'getList')
.and.returnValue(observable);
});
it('should create an instance', () => {
expect(component).toBeTruthy();
});
it('should return a response', () => {
fixture.detectChanges();
expect(component.books).toEqual([
{ 'name': 'Whiteboard Interviews' }
]);
});
});