Search code examples
angulartypescriptkarma-jasmineangular-httpclient

Unit test for a function with more than one HTTP request


I have a problem with writing a unit test for a function which indirectly calls more than one HTTP request.

Service which is being tested has the following structure:

/* content.service.ts */
import { Injectable } from "@angular/core"
import { ApiService } from "services/api/api.service"

@Injectable()
export class ContentService {
  public languages: Array<any>
  public categories: Array<any>

  constructor(private apiService: ApiService) {}

  public async fetchLanguages(): Promise<boolean> {
    const data: any = await this.apiService.getData("language")

    if (!data) return false

    this.languages = data
    return true
  }

  public async fetchCategories(): Promise<boolean> {
    const data: any = await this.apiService.getData("category")

    if (!data) return false

    this.categories = data
    return true
  }

  public async initService(): Promise<boolean> {
    const statusLanguages: boolean = await this.fetchLanguages()

    if (!statusLanguages) return false

    const statusCategories: boolean = await this.fetchCategories()

    if (!statusCategories) return false

    return true
  }
}

Service test file looks like this:

/* content.service.spec.ts */
import {
  HttpClientTestingModule,
  HttpTestingController
} from "@angular/common/http/testing"
import { TestBed } from "@angular/core/testing"

import { ApiService } from "services/api/api.service"
import { ContentService } from "./content.service"

describe("ContentService:", () => {
  let contentService: ContentService
  let httpMock: HttpTestingController

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ HttpClientTestingModule ],
      providers: [ ApiService, ContentService ]
    })

    contentService = TestBed.get(ContentService)
    httpMock = TestBed.get(HttpTestingController)
  })

  afterEach(() => {
    httpMock.verify()
  })

  /* Dummy data */
  const languages = [
    { id: 1, uid: "en", active: true },
    { id: 2, uid: "es", active: false },
    { id: 3, uid: "hr", active: false }
  ]

  const categories = [
    { id: 1, uid: "default" },
    { id: 2, uid: "alternative" }
  ]

  describe("initService:", () => {
    it("successful response", (done: DoneFn) => {
      contentService.initService().then(result => {
        expect(result).toEqual(true)
        expect(contentService.languages).toEqual(languages)
        expect(contentService.categories).toEqual(categories)
        done()
      })

      const requests = httpMock.match(req => !!req.url.match(/api\/data/))

      expect(requests.length).toBe(2)

      requests[0].flush({ status: "ok", data: languages })
      requests[1].flush({ status: "ok", data: categories })
    })
  })
})

When running unit tests with ng test following error is being thrown:

TypeError: Cannot read property 'flush' of undefined

TypeError is related to the line:

requests[1].flush({ status: "ok", data: categories })

Which brings me to a conclusion that first request is correctly handled, while the second one is not.

Shouldn't method match from the HttpTestingController catch all HTTP requests which match with the provided regular expression?


Solution

  • When writing unit tests you should test only one unit. In this case your unit is ContentService. Keep in mind that you dont have to test it's dependencies. As I see ApiService is a depedency in your service which is used to make Http Requests. So You dont have test whether that you are making http requests or not. You just have to mock the ApiService using a mock value or jasmine spy object.

      const spy = jasmine.createSpyObj('ApiService', ['getData']);
    
      TestBed.configureTestingModule({
        providers: [
          ContentService,
          { provide: ApiService, useValue: spy }
        ]
      });
    

    What you have to test here is the variables inside your ContentService are getting the correct values and the return values of the methods according to the input values.

    You can test your ApiService separately. Do not try to test the dependencies of your classes when you're unit testing.