How to test Angular services with the HttpClient.

So you can shake the bugs outta your Angular code.

Photo by Wynand Uys on Unsplash

A common dependency inside an Angular service file is the HttpClient? 😸

But just how do you test a service that depends on the HttpClient?

Until…

Should he use a unit test? Or an integration test?

Should you mock the Angular HttpClient? Or call a real API service?

Write your first unit test for an Angular service

npm i --include=dev jasmine-auto-spies
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { createSpyFromClass, Spy } from 'jasmine-auto-spies';
import { Customer } from '../models/customer';

import { CustomersService } from './customers.service';

describe('CustomersService', () => {
let service: CustomersService;
let httpSpy: Spy<HttpClient>;
let fakeCustomers: Customer[] = [
{
id: "1",
name: "Fake Customer",
email: "fake@fake.com"
},
{
id: "2",
name: "Fake Customer Two",
email: "fake-two@fake.com"
}
];

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
CustomersService,
{ provide: HttpClient, useValue: createSpyFromClass(HttpClient) }
]
});

service = TestBed.inject(CustomersService);
httpSpy = TestBed.inject<any>(HttpClient);
});

it('should return an expected list of customers', (done: DoneFn) => {
httpSpy.get.and.nextWith(fakeCustomers);

service.getAllCustomers().subscribe(
customers => {
expect(customers).toHaveSize(fakeCustomers.length);
done();
},
done.fail
);
expect(httpSpy.get.calls.count()).toBe(1);
});
});

Writing tests for a POST request

import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { createSpyFromClass, Spy } from 'jasmine-auto-spies';
import { Customer } from '../models/customer';

import { CustomersService } from './customers.service';

describe('CustomersService', () => {
let service: CustomersService;
let httpSpy: Spy<HttpClient>;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
CustomersService,
{ provide: HttpClient, useValue: createSpyFromClass(HttpClient) }
]
});

service = TestBed.inject(CustomersService);
httpSpy = TestBed.inject<any>(HttpClient);
});

it('should create a new customer', (done: DoneFn) => {

var newCustomer = {
name: "New Customer",
email: "new@customer.com"
} as Customer;

httpSpy.post.and.nextWith(newCustomer);

service.createCustomer(newCustomer).subscribe(
customer => {
expect(customer).toEqual(newCustomer);
done();
},
done.fail
);
expect(httpSpy.post.calls.count()).toBe(1);
});
});

Writing tests for a PUT request

import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { createSpyFromClass, Spy } from 'jasmine-auto-spies';
import { Customer } from '../models/customer';

import { CustomersService } from './customers.service';

describe('CustomersService', () => {
let service: CustomersService;
let httpSpy: Spy<HttpClient>;
let fakeCustomers: Customer[] = [
{
id: "1",
name: "Fake Customer",
email: "fake@fake.com"
},
{
id: "2",
name: "Fake Customer Two",
email: "fake-two@fake.com"
}
];

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
CustomersService,
{ provide: HttpClient, useValue: createSpyFromClass(HttpClient) }
]
});

service = TestBed.inject(CustomersService);
httpSpy = TestBed.inject<any>(HttpClient);
});

it('should update a customer with given customer id', (done: DoneFn) => {

var customer = fakeCustomers[0];
customer.name = "Updated Customer";

httpSpy.put.and.nextWith(customer);

service.updateCustomer(customer).subscribe(
customer => {
expect(customer.name).toEqual("Updated Customer");
done();
},
done.fail
);
expect(httpSpy.put.calls.count()).toBe(1);
});
});

Writing tests for a DELETE request

import { HttpClient, HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { createSpyFromClass, Spy } from 'jasmine-auto-spies';
import { Customer } from '../models/customer';

import { CustomersService } from './customers.service';

describe('CustomersService', () => {
let service: CustomersService;
let httpSpy: Spy<HttpClient>;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
CustomersService,
{ provide: HttpClient, useValue: createSpyFromClass(HttpClient) }
]
});

service = TestBed.inject(CustomersService);
httpSpy = TestBed.inject<any>(HttpClient);
});

it('should be created', () => {
expect(service).toBeTruthy();
});

it('should delete an existing customer', (done: DoneFn) => {

httpSpy.delete.and.nextWith(new HttpResponse ({
status: 200
}));

service.deleteCustomer("1").subscribe(
response => {
expect(response.status).toEqual(200);
done();
},
done.fail
);
expect(httpSpy.delete.calls.count()).toBe(1);
});
});

Writing tests for a 404 error

import { HttpClient, HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { createSpyFromClass, Spy } from 'jasmine-auto-spies';
import { Customer } from '../models/customer';

import { CustomersService } from './customers.service';

describe('CustomersService', () => {
let service: CustomersService;
let httpSpy: Spy<HttpClient>;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
CustomersService,
{ provide: HttpClient, useValue: createSpyFromClass(HttpClient) }
]
});

service = TestBed.inject(CustomersService);
httpSpy = TestBed.inject<any>(HttpClient);
});

it('should return a 404', (done: DoneFn) => {

var customerId = "89776683";

httpSpy.get.and.throwWith(new HttpErrorResponse({
error: "404 - Not Found",
status: 404
}));

service.getCustomer(customerId).subscribe(
customer => {
done.fail("Expected a 404");
},
error => {
expect(error.status).toEqual(404);
done();
}
);
expect(httpSpy.get.calls.count()).toBe(1);
});
});

Conclusion