#Unit testing services in angular 16

6 messages · Page 1 of 1 (latest)

wanton wave
#

I'm fairly new to unit testing and can't seem to unit test a simple service, even using the docs...

Here's my service, I want to test my getBooks Method

import { Injectable } from '@angular/core';
import { Observable, map, of } from 'rxjs';

import { HttpClient } from '@angular/common/http';


export interface Book {
  id: string,
  volumeInfo: {
      title: string,
      authors: Array<string>,
  };
}

@Injectable({
  providedIn: 'root'
})
export class BooksService {

  constructor(private http: HttpClient) { }

  getBooks(): Observable<Array<Book>> {
    return this.http.get<{ items: Book[] }>(
        'https://www.googleapis.com/books/v1/volumes?maxResults=5&orderBy=relevance&q=oliver%20sacks'
      ) .pipe(map((books) => books.items || []));
  }
}
#

And here's my unit test which is failing on the second test, where the result has no values and the expected result had 2 values

import { TestBed } from '@angular/core/testing';
import { BooksService } from './books.service';
import { Book } from '../models/books.model';
import { HttpClient } from '@angular/common/http';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { defer } from 'rxjs';
const expectedBooks: Array<Book> = [
  {
    id: 'firstId',
    volumeInfo: {  title: 'First Title', authors: ['First Author'] }
  },
  {
    id: 'secondId',
    volumeInfo: { title: 'Second Title', authors: ['Second Author'] }
  }
];

describe('BooksService', () => {
  let service: BooksService;
  let httpClientSpy: jasmine.SpyObj<HttpClient>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
    });
    service = TestBed.inject(BooksService);
    httpClientSpy = TestBed.inject(HttpClient) as jasmine.SpyObj<HttpClient>;

    // the following (as per docs) fails due to timeout after 5 seconds?:
    //httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);
  });

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

//the following test fails =>
  it('should return expected books (HttpClient called once)', (done: DoneFn)  => {
    httpClientSpy.get.and.returnValue(asyncData(expectedBooks));

    service.getBooks().subscribe({
      next: books => {
        expect(books)
          .withContext('expected books ') //the object "books" here has a length of 0 and "expectedBooks" has a length of 
          .toEqual(expectedBooks);
        done();
      },
      error: done.fail
    });
  });
});
// function was required according to the online sample
// ref. https://stackblitz.com/run?file=src%2Fapp%2Fmodel%2Fhero.service.spec.ts,src%2Ftesting%2Fasync-observable-helpers.ts
export function asyncData<T>(data: T) {
  return defer(() => Promise.resolve(data));
}
gaunt furnace
#

Besides, the code expects the server to return an object such as { items: [...] }, bit that's not what you tell the spy to return.

wanton wave
gaunt furnace
#

This is another way (not as good, IMHO), but then,

  • you shouldn't use the HttpClientTestingModule,
  • you should create a mock HttpClient, as done in the example,
  • you should pass the mock to the service by constructing it yourself as done in the example, or by providing it in the testing module