2

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!

1 Answer 1

4

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

  • method calls will fail in runtime
  • instanceof check fails

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.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.