2016-09-20 58 views
3

我有以下幾個角度2.0.0組件:測試角2.0.0組件與HTTP請求

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()功能?

我不想包括我迄今爲止嘗試過的測試,因爲我懷疑自己走的是正確的道路,我不想偏見答案。

+0

你是什麼意思測試?你想檢查它是否被調用,或者是什麼? –

+0

好問題。我想提供一個模擬響應,並期望'BookListComponent'的'books'屬性被設置爲模擬響應的值。 –

回答

1

要模擬Http,您需要在中配置MockBackendHttp提供程序。然後,你可以訂閱其連接的提供模擬響應

beforeEach(() => { 
    TestBed.configureTestingModule({ 
     providers: [ 
     { 
      provide: Http, useFactory: (backend, options) => { 
      return new Http(backend, options); 
      }, 
      deps: [MockBackend, BaseRequestOptions] 
     }, 
     MockBackend, 
     BaseRequestOptions 
     ] 
    }); 
}); 

我如何將考驗ngOnInit()函數?

問題是Http.get調用的異步性質。當您調用fixture.detectChanges()時,將調用ngOnInit,但Http的異步特性會導致測試在Http完成之前運行。爲此,我們使用fakeAsync,如here所述,但接下來的問題是您不能使用fakeAsynctemplateUrl。你可以用setTimeout來攻擊它並在那裏測試。這將工作。我個人雖然不喜歡它。

it('', async(() => { 
    setTimeout(() => { 
    // expectations here 
    }, 100); 
}) 

就我個人而言,我認爲你有一個設計缺陷開始。應該將Http調用抽象爲服務,並且組件應該與服務交互,而不是直接與Http進行交互。如果您將設計更改爲(我會推薦),那麼您可以測試服務like in this example,並且對於組件測試,創建一個同步模擬,如this post中所述。

這裏是會怎麼做

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'); 
    }); 
    }))); 
}); 
+0

我接受了你的答案,因爲它讓我找到了一個解決方案,儘管我最終做的事情大不相同。我添加了自己的答案,以便人們可以看到我結束的代碼。 –

1

其實我最終做的東西有點不同的一個完整的例子。

根據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' } 
    ]); 
    }); 
}); 
+0

我並不完全確定,但我想說,仍然有可能的競爭條件。我不是觀察者的專家,但是你是如何創建觀察者使其同步的?它有點像我。 –

+0

我相信關鍵是'detectChanges()'。我的理解是,它導致觀察者解決。可能是錯的 - 我是新來的這個東西。 –