2017-02-13 57 views
1

我有一个使用返回Observable的服务的组件。我没有在我的茉莉花测试中连接这项服务,而是选择了窥探模拟。使用Observable(Angular 2)嘲笑服务时测试出错

下面是一个使用HTTP来获取从web服务器的JSON响应的NumProbesService [numprobes.service.ts]:

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.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',您会看到什么?附注:看起来像你的间谍和模拟正在做同样的事情TWICE:因为你的模拟已经返回模拟数据,为什么你需要窥探还返回模拟数据? – AngularChef

+0

@AngularFrance,我在调用subscribe()之前添加了'console.log(this.numProbesService.getProbeCount());'并且获得了'LOG:ProbeInfo {}'。这是否意味着getProbeCount()被调用时,我没有从模拟服务中获得Observable?该方法明显地返回'Observable '虽然。你看到我可能会出错吗? – Melvin

+0

你能告诉我你在测试中要验证什么吗?(没有提到你怎么做,我有兴趣知道你想做什么) – AngularChef

回答

3

你得到的错误是由于间谍覆盖原来的方法

换句话说,执行该行后:

spyOn(numProbesService, 'getProbeCount') 

numProbesService.getProbeCount不再引用您返回可观察到的原始方法,它引用了间谍。

我成立了一个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