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?
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.