We now understand the importance of testing in isolation I want to shift our focus to testing an HTTP Service. By the time we wrap up this addition to the series, not only will you understand how to write valuable tests, but you'll also understand what to test - something I feel a lot of newcomers to unit testing struggle to wrap their minds around.

<aside> 📢 If you haven't read parts one and two, I encourage you to come back to this article after reviewing the foundational concepts laid out in those posts.

</aside>

Understanding the setup

For the purposes of this article, I've created a new Angular application and bootstrapped a json-server into the project so we can make API requests and compliment our learning process. By default, this API is running on localhost:3000.

If you'd like to follow along, feel free to clone down this repo before continuing! I've created a starting branch that has everything you need to follow along!

Altering the karma.config with ChromeHeadless

When you run ng test in a new Angular project, the Karma report will be opened in a new Chrome tab. I prefer to have my test results shown in the terminal. To make this change, alter the browsers property in your karma.config.js file.

module.exports = function(config) {
	config.set({
	...
	browsers: ['ChomeHeadless'],
	});
}

The Angular HTTP Service We Will Be Unit Testing

I have created a very simplistic HTTP service with all of the CRUD operations. Take a look below.

@Injectable({
  providedIn: 'root',
})
export class BooksService {
  url = 'localhost:3000/';

  httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
  };

  constructor(private http: HttpClient) {}

  getAllBooks(): Observable<Book[]> {
    return this.http
      .get<Book[]>(`${this.url}/books`)
      .pipe(catchError(this.handleError<Book[]>('getAllBooks', [])));
  }

  getBookById(id: number): Observable<Book> {
    return this.http
      .get<Book>(`${this.url}/books/${id}`)
      .pipe(catchError(this.handleError<Book>(`getBookById id=${id}`)));
  }

  updateBook(book: Book): Observable<any> {
    return this.http
      .put(`${this.url}/books`, book, this.httpOptions)
      .pipe(catchError(this.handleError<any>(`updateBook`)));
  }

  addBook(book: Book): Observable<Book> {
    return this.http
      .post<Book>(`${this.url}/books`, book, this.httpOptions)
      .pipe(catchError(this.handleError<Book>(`addBook`)));
  }

  deleteBook(book: Book): Observable<Book> {
    return this.http
      .delete<Book>(`${this.url}/books/${book.id}`, this.httpOptions)
      .pipe(catchError(this.handleError<Book>(`deleteBook`)));
  }

  private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
      console.error(`${operation} failed: ${error.message}`);

      return of(result as T);
    };
  }
}

If you feel uncomfortable with any of these functions and what they are doing or the various operators in play, read the official Angular documentation about creating HTTP services.

<aside> 📢 I have defined the URL here in the service, but ideally this would be sourced from an environment variable defined in your project.

</aside>

What Do I Need to Unit Test?