I have a component that uses a service that returns an Observable. Instead of wiring up this service in my Jasmine test, I've chosen to spy on a mock.
Here is the NumProbesService [numprobes.service.ts] that uses Http to get a JSON response from a web server:
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'));
}
}
I'm mocking this service up with 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);
}
}
The ProbeInfo [probeinfo.model.ts] class is here:
export class ProbeInfo {
private online : boolean;
private accepted: boolean;
private probeCount: number;
constructor(probeCount: number) {
}
}
The component that I'm testing is here:
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;
}
);
}
}
Finally, here is the actual test for the component.
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');
});
}
So that brings me to the problem. One of my tests is failing. After fixture.detectChange() it looks like the component is initialized and the getProbeCount() is executed on the mock service, NumProbesMockService.
It says this.numProbesService.getProbeCount(...).subscribe is not a function
. How could that be? this.numProbesService.getProbeCount() returns an Observable which has a subscribe method, right?
Here is the complete error. Any help would be greatly appreciated.
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)