This is my Service-class job.service.ts. It is basically calling a Express app and getting a Job-object as result. Which it is returning as Observable to the caller of the getJob() method.
import { Injectable } from '@angular/core';
import {Job} from "./domain-models/Job";
import {Observable} from 'rxjs';
import {HttpClient} from "@angular/common/http";
@Injectable({
providedIn: 'root'
})
export class JobService {
readonly ROOT_URL = 'http://localhost:3000';
constructor(private http: HttpClient) { }
getJob(jobId: string): Observable<any> {
if(jobId){
return this.http.get<Job>(`${this.ROOT_URL}/jobs/${jobId}`);
}else{
return new Observable<Error>( subscriber => {
subscriber.next(new Error('No jobID provided));
})
}
}
}
This is a spec from my JobService test class. with the spyOn method I intercept every attempt to call the real API and return a fake response which apparently is an instance of Job. job.service.spec.ts:
describe('Testing the getJob() function of JobService', ()=>{
beforeEach(()=>{
let jobId: string = 'A123456789'
const job: Job = {
actualCost: 100,
amountPaid: 50,
wishAnalysis: false
};
spyOn(httpService, "get").and.returnValue(of<Job>(job));
})
it('should return an Observable with the corresponding job object matching the id',()=>{
let jobIdPassed: string = 'A123456789'
service.getJob(jobIdPassed).subscribe(value => {
expect(value instanceof Job).toBeTrue();
});
})
})
If I execute
expect(value instanceof Job).toBeTrue();
the test fails because it seems that value is not an instance of Job. If I change the JobServices getJob() function and return a manually created Observable like so:
getJob(jobId: string): Observable<any> {
if(jobId){
return new Observable<Job>(subscriber => {
subscriber.next(new Job());
subscriber.complete();
})
}else{
return new Observable<Error>( subscriber => {
subscriber.next(new Error('No jobID provided));
})
}
}
The test works! So the problem is why is HttpClient librarys get() method not providing the response in the appropriate type inside an Observable like it's supposed to?
I appreciate that you took the time to read it and for any help!
Problem 1: Incorrect setup of test data
The job
you create in your test is not the instance of Job
class:
class Job {
constructor(public actualCost: number,
public amountPaid: number,
public wishAnalysis: boolean) {}
}
const job: Job = {
actualCost: 100,
amountPaid: 50,
wishAnalysis: false
};
console.log(job);
console.log(job instanceof Job); //false
Problem 2: Incorrect assumptions about http.get
This is a common pitfall - http.get<T>
is only a convenience generic
http.get<T>
returns parsed json object.
T
is used only for convenience, the expectation is that it describes the shape of your data correctly. But no runtime check is performed - missing properties, additional properties, nothing will be checked (and fail later in runtime, when you access the data).
In particular, if T is a class, you are bound to have issues, as the returned data will not be the instance of T in runtime - it may have the fields filled in, if names match, but prototype chain is not set up. This means that
Solution
Don't use a class to model the shape of data aquired with http.get
.
Use a type (or interface) instead. If you need a class, map the returned data.