2017-02-13 4 views
1

У меня есть компонент, который использует службу, которая возвращает Observable. Вместо того, чтобы подключить эту услугу в своем тесте Jasmine, я решил шпионить за макетом.Ошибка при тестировании при изнасиловании с помощью Observable (Angular 2)

Вот NumProbesService [numprobes.service.ts], который использует Http, чтобы получить ответ JSON из веб-сервера:

import { Injectable } from '@angular/core'; 
import { Http, Response, Headers, RequestOptions } from '@angular/http'; 
import {Observable} from 'rxjs/Rx'; 
import 'rxjs/add/operator/map' 
import {ProbeCount} from "./probecount.model"; 

@Injectable() 
export class NumProbesService { 

    private numProbesUrl = 'http://localhost:9090/getnumberofprobes'; 
    private probeCount: ProbeCount; 

    constructor (private http: Http) {} 

    createAuthorizationHeader(headers: Headers) { 
    headers.append('X-Auth-Key', 'mjones'); 
    headers.append('X-Auth-Secret', '111111-2222222-22222-3233-4444444'); 
    } 


    public getProbeCount() : Observable<ProbeCount> { 
    let headers = new Headers(); 
    this.createAuthorizationHeader(headers); 

    return this.http.get(this.numProbesUrl, {headers: headers}) 
     .map((response:Response) => this.probeCount = <ProbeCount>response.json()) 
     .catch((error:any) => Observable.throw(error.json().error || 'Server error')); 
    } 

} 

Я насмешливо эту услугу с NumProbesMockService [numprobes.service.mock .ts]:

import { Observable } from 'rxjs/Observable'; 
import { ProbeInfo } from './probeinfo.model'; 

export class NumProbesMockService { 

    probeInfo : ProbeInfo = new ProbeInfo(3); 

    public getProbeCount(): Observable<ProbeInfo> { 
    return Observable.of(this.probeInfo); 
    } 

} 

ProbeInfo [probeinfo.model.ts] класс здесь:

export class ProbeInfo { 

    private online : boolean; 
    private accepted: boolean; 
    private probeCount: number; 

    constructor(probeCount: number) { 

    } 

} 

Компонент, который я тестирую здесь:

import {Component, Input} from '@angular/core'; 
import {NumProbesService} from './numprobes.service'; 
import {ProbeCount} from "./probecount.model"; 

@Component({ 
    selector: 'numprobes-box', 
    templateUrl: './numprobes.component.html' 
}) 
export class NumProbesComponent { 

    name: string; 
    numprobes: number; 
    probeCount: ProbeCount; 

    constructor(private numProbesService: NumProbesService) { 

    } 

    ngOnInit() { 
    this.name = "Number of Probes"; 

    this.numProbesService.getProbeCount().subscribe(
     (probeCount) => { 
     console.log("probeCount: " + JSON.stringify(probeCount)); 
     console.log(probeCount.total_probe_count); 
     this.numprobes = probeCount.total_probe_count; 
     } 
    ); 
    } 
} 

И, наконец, вот фактическое испытание компонента.

import {By} from '@angular/platform-browser'; 
import {DebugElement} from '@angular/core'; 

import {ComponentFixture, TestBed} from '@angular/core/testing'; 
import {NumProbesService} from './numprobes.service'; 
import {NumProbesMockService} from './numprobes.service.mock'; 
import {NumProbesComponent} from './numprobes.component'; 
import {ProbeInfo} from './probeinfo.model'; 


describe('NumProbesComponent',() => { 

    let comp: NumProbesComponent; 
    let fixture: ComponentFixture<NumProbesComponent>; 
    let spy: jasmine.Spy; 
    let de: DebugElement; 
    let el: HTMLElement; 
    let numProbesService: NumProbesService; // the actually injected service 

    const numProbes = 5; 
    let probeInfo : ProbeInfo = new ProbeInfo(numProbes); 


    beforeEach(() => { 
    TestBed.configureTestingModule({ 
     declarations: [NumProbesComponent], 
     providers: [ 
     { provide: NumProbesService, useClass: NumProbesMockService } 
     ] 
    }); 


    fixture = TestBed.createComponent(NumProbesComponent); 
    comp = fixture.componentInstance; 

    numProbesService = fixture.debugElement.injector.get(NumProbesService); 

    spy = spyOn(numProbesService, 'getProbeCount') 
     .and.returnValue(probeInfo); 
    }); 

    it('Should show the label within component',() => { 

    de = fixture.debugElement.query(By.css(".info-box-text")); 
    el = de.nativeElement; 

    fixture.detectChanges(); 
    expect(el.textContent).toBe('Number of Probes', 'Label displayed'); 

    }); 


    it('should show the name of the info box, "Number of Probes"',() => { 

    de = fixture.debugElement.query(By.css(".info-box-number")); 
    el = de.nativeElement; 

    console.log("el.textContent: " + el.textContent); 

    expect(el).toBeDefined(); 
    expect(el.textContent).toBe('', 'nothing displayed'); 

    let probeInfoCalled = numProbesService.getProbeCount(); 

    expect(spy.calls.any()).toBe(true, 'getProbeCount not yet called'); 

    }); 
} 

Так что это привело меня к проблеме. Один из моих тестов терпит неудачу. После fixture.detectChange() он выглядит как инициализируется компонент, и getProbeCount() выполняется в макетной службе NumProbesMockService.

В нем указано this.numProbesService.getProbeCount(...).subscribe is not a function. Как это могло быть? this.numProbesService.getProbeCount() возвращает Observable, у которого есть метод подписки, правильно?

Вот полная ошибка. Любая помощь будет принята с благодарностью.

Chrome 56.0.2924 (Mac OS X 10.10.5) NumProbesComponent Should show the label within component FAILED 
    Error: Error in :0:0 caused by: this.numProbesService.getProbeCount(...).subscribe is not a function 
     at ViewWrappedError.ZoneAwareError (webpack:///~/zone.js/dist/zone.js:811:0 <- src/test.ts:106351:33) 
     at ViewWrappedError.BaseError [as constructor] (webpack:///~/@angular/core/src/facade/errors.js:26:0 <- src/test.ts:6476:16) 
     at ViewWrappedError.WrappedError [as constructor] (webpack:///~/@angular/core/src/facade/errors.js:88:0 <- src/test.ts:6538:16) 
     at new ViewWrappedError (webpack:///~/@angular/core/src/linker/errors.js:73:0 <- src/test.ts:60069:16) 
     at CompiledTemplate.proxyViewClass.DebugAppView._rethrowWithContext (webpack:///~/@angular/core/src/linker/view.js:650:0 <- src/test.ts:84195:23) 
     at CompiledTemplate.proxyViewClass.DebugAppView.detectChanges (webpack:///~/@angular/core/src/linker/view.js:623:0 <- src/test.ts:84168:18) 
     at ViewRef_.detectChanges (webpack:///~/@angular/core/src/linker/view_ref.js:179:0 <- src/test.ts:61015:20) 
     at ComponentFixture._tick (webpack:///~/@angular/core/bundles/core-testing.umd.js:191:0 <- src/test.ts:12899:36) 
     at webpack:///~/@angular/core/bundles/core-testing.umd.js:205:45 <- src/test.ts:12913:53 
     at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:242:0 <- src/test.ts:105782:26) 
     at ProxyZoneSpec.onInvoke (webpack:///~/zone.js/dist/proxy.js:79:0 <- src/test.ts:71475:39) 
     at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:241:0 <- src/test.ts:105781:32) 
     at Object.onInvoke (webpack:///~/@angular/core/src/zone/ng_zone.js:269:0 <- src/test.ts:31712:37) 
     at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:241:0 <- src/test.ts:105781:32) 
     at Zone.run (webpack:///~/zone.js/dist/zone.js:113:0 <- src/test.ts:105653:43) 
+0

Что вы видите, если вы используете console.log 'numProbesService.getProbeCount' прямо перед подпиской? Боковое примечание: похоже, ваш шпион И макет делает то же самое ДВАЖДЫ: так как ваш макет уже возвращает макет данных, зачем вам шпионить за возвратом ложных данных? – AngularChef

+0

@AngularFrance, я добавил 'console.log (this.numProbesService.getProbeCount());' к компоненту непосредственно перед вызовом subscribe() и получил 'LOG: ProbeInfo {}'. Означает ли это, что я не получаю Observable из моего макетного сервиса, когда вызывается getProbeCount()? Метод явно возвращает 'Observable ' хотя. Вы видите, где я могу ошибаться? – Melvin

+0

Можете ли вы рассказать мне, что вы пытаетесь проверить в своем тесте?(не говоря о том, КАК вы пытаетесь это сделать, мне интересно узнать, что вы пытаетесь сделать) – AngularChef

ответ

3

Ошибка вы получаете из-за шпиона Переопределение оригинальный метод.

Другими словами, после того, как эта линия выполнена:

spyOn(numProbesService, 'getProbeCount') 

numProbesService.getProbeCount больше не ссылается на свой оригинальный метод, который возвращает Observable, она ссылается на шпиона.

Я создал Plunker, чтобы проверить мой ответ, и он работает, как ожидалось: если вы закомментировать шпиона, вы сможете подписаться снова:

// app.component.spec.ts 

beforeEach(async(() => { 
    ps = fixture.debugElement.injector.get(ProbesService); 

    // The spy overrides the ps.getProbeCount() method. 
    //spyOn(ps, 'getProbeCount'); 

    // trigger initial data binding 
    fixture.detectChanges(); 
})); 

it('should return 5', (done) => { 
    // This will NOT work if the spy has been set up. 
    ps.getProbeCount().subscribe(val => { 
    expect(val).toEqual(5); 
    done(); 
    }); 
}); 

it('should have been called',() => { 
    // This will work ONLY IF the spy has been set up. 
    expect(ps.getProbeCount).toHaveBeenCalled(); 
}); 

Но как я уже сказал в комментарии, вы делать то же самое дважды. Либо используйте шпион ИЛИ макет, чтобы вернуть значение макета, но не оба.

+1

Да, действительно! Так оно и было. Я тоже шпион и насмехался, и я должен был делать то или другое. Я избавился от шпиона и просто использовал макет, и все работает нормально. – Melvin

+0

Отлично. Если вы не возражаете, пожалуйста, пометьте ответ как «правильный», чтобы другие люди в StackOverflow могли использовать его в будущем. Спасибо, Мелвин! – AngularChef

Смежные вопросы