DEV Community

Bearded JavaScripter
Bearded JavaScripter

Posted on

8 4

TDD in Angular - Basics of HTTP Testing

It won't be much of a Web Application if it doesn't make HTTP requests, does it? However, it's easy to forget that these need to be tested as well!

In this article, I'll cover how to unit test HTTP requests in your Angular Application so you can have the confidence that they'll always work as expected.

Code for the article can be found here.

Setting up our code

Let's say that our HTTP calls live in a service that handles to-do items. We can do our basic CRUD operations: getting our to-do items or a single one, creating new items, updating or deleting items.

I'll be using JSONPlaceHolder for this since it's the quickest way to get started. The most barebones CRUD service should look like this:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
export interface Todo {
userId: number,
id: number,
title: string,
completed: boolean;
}
@Injectable({
providedIn: 'root'
})
export class TodoService {
url: string = 'https://jsonplaceholder.typicode.com/todos';
constructor(private http: HttpClient) {}
getAllTodos() {
return this.http.get(this.url)
}
getSingleTodo(id: number) {
return this.http.get(`${this.url}/${id}`);
}
createTodo(item: Todo) {
return this.http.post(this.url, item);
}
updateTodo(id: number, updatedItem: Todo) {
return this.http.put(`${this.url}/${id}`, updatedItem);
}
deleteTodo(id: number) {
return this.http.delete(`${this.url}/${id}`);
}
displayError(message: string) {
console.log(message);
}
}
view raw todo.service.ts hosted with ❤ by GitHub

After importing HttpClientTestingModule, the auto-generated spec file should look like this:

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TodoService } from './todo.service';
fdescribe('TodoService', () => {
let service: TodoService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
service = TestBed.inject(TodoService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

At this point your only test ("it should be created") should be passing.

HTTP Testing Method

Remember that during testing, we're not actually making any requests. We're only concerned with if the request was sent and if a response is handled properly when it returns. Anything that happens in between that is out of the scope of unit testing.

Therefore, let's say that we're unit testing a GET request, we only care that the request is sent and, if the request is successful, some data comes back.

Keep in mind that Angular HttpClient uses Observables rather than promises so we have to adjust our tests accordingly!

Angular provides an entire module to help us test HTTP Requests called the HttpClientTestingModule that allows us to:

  • Mock a request
  • Create a fake request with any status code
  • Pass along a response with fake data
  • Cancel a request
  • And much much more!

This means we can test our requests from every angle possible and have our application handle as much cases as possible. I'll cover these in future articles. Let's get set up!

Setting up HTTP Mocks

We need to establish a Mock network that allows us to control when requests are sent, what data is returned and whether or not the request was successful. This comes in the form of HttpTestingController as seen below:

fdescribe('TodoService', () => {
let service: TodoService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
service = TestBed.inject(TodoService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
})
it('should be created', () => {
expect(service).toBeTruthy();
});
});

Notice that in the afterEach block, we called httpMock.verify(). This ensures that there are no pending requests in our mock network before moving on to other tests.

Now we can really start testing our code!

Testing Requests

We want to test getSingleTodo. Based on the function name, we can expect that the HTTP response will contain a todo object. Let's represent our expectations as tests.

it('getSingleTodo should send a GET request and return a single item', (done) => {
service.getSingleTodo(1).subscribe(
(item: Todo) => { expect(item).toBeDefined(); done(); },
(error) => { fail(error.message) }
);
const testRequest = httpMock.expectOne('https://jsonplaceholder.typicode.com/todos/1');
expect(testRequest.request.method).toBe('GET');
testRequest.flush({ id: 1, userId: 1, title: 'Test Todo', completed: false });
});

In the above code, we:

  • ran our getSingleTodo function and expected the result to be defined
  • used the controller to expect a request to have the URL https://jsonplaceholder.typicode.com/todos/1
  • expected the request to be a GET request
  • used the controller to manually send the request with the fake todo data.

Under normal circumstances, the getSingleTodo function would have made an actual request but our HttpTestingController intercepted the request and returned the fake data using testRequest.flush.

We can now use this information to test POST requests. These contain a request body and can return data along with the status code.

it('createTodo should send a POST request and return the newly created item', (done) => {
const item: Todo = {
id: 2,
userId: 2,
title: 'Walk dog',
completed: false
};
service.createTodo(item).subscribe(
(data: Todo) => {
expect(data).toBeDefined();
expect(data).toEqual(item);
done()
},
(error) => {
fail(error.message)
}
);
const testRequest = httpMock.expectOne('https://jsonplaceholder.typicode.com/todos');
expect(testRequest.request.method).toBe('POST');
testRequest.flush(item);
});

This test ensures a POST request is sent to the correct URL and the created data is returned.

Conclusion

In this article, we learned:

  • the theory behind testing HTTP requests by using a mock network
  • setting up the mock network using HttpTestingController
  • running fake requests and testing their URLs, methods and return values

The next article will show how to control fake requests so we can test our application in the event of unauthorized requests, server crashes and basic error handling.

Hope you enjoyed reading! 😄

AWS Q Developer image

Your AI Code Assistant

Ask anything about your entire project, code and get answers and even architecture diagrams. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Start free in your IDE

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay