Créer des tests unitaires avec Angular [Part 2] : Mocker un service

Dès que l'on fait des tests unitaires, on se retrouve très rapidement face à la problématique des Mocks.

Par définition, un test unitaire ne doit tester qu'une infime partie de votre code. Prenons l'exemple d'une méthode d'un service A utilise une méthode d'un service B qui utilise elle même une méthode d'un service C. Si votre test unitaire plante, vous ne saurez pas si c'est de la faute de A, B ou C.

D'ou l'idée de Mock, c'est-à-dire d'utiliser des objets qui simulent le comportement d'objets tiers. Prenons un exemple concret avec un service Angular qui retourne un IUser d'une requête Http :

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

  constructor(private httpClient: HttpClient) { }

  getUser(id: number): Observable<IUser> {
    const url = environment.apiUrl + 'users/' + id;
    return this.httpClient.get<IUser>(url);
  }
}

Si l'on veut tester cette méthode, il n'est pas question de faire un vrai appel Http vers notre api. Pour peu de le site ne soit pas disponible, notre test unitaire planterait même si le code est valide, ce qui est contraire au principe des tests unitaires.

La bonne nouvelle c'est que jasmine nous permet de créer des espions, des mocks assez facilement avec la méthode createSpyObj :

httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);

Le premier argument est le nom de l'objet que l'on veut mocker, le second la liste des méthodes que l'on veut gérer. Ensuite, on doit définir le comportement de cette méthode. Pour notre test, on se moque (ah, ah ah) de ce qu'elle retourne. Ce qui nous importe c'est que l'api soit appelée avec la bonne url :

httpClientSpy.get.and.returnValue(of(jasmine.any('IUser')));

Il ne nous reste plus qu'à appeler la méthode de notre service :

usersService.getUser(1).subscribe(result => {
      expect(httpClientSpy.get).toHaveBeenCalledWith('https://monSite.fr/api/users/1');
    });

Quand on appele la méthode getUser avec le paramètre 1, on veut être sur que le service HttpClient appele l'url https://monSite.fr/users/1. Notre test unitaire complet est donc le suivant :

import { UsersService } from './users.service';
import { of } from 'rxjs';

describe('UsersService', () => {
  let usersService: UsersService;
  let httpClientSpy: { get: jasmine.Spy };

  beforeEach(() => {
    httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);
    usersService = new UsersService(<any>httpClientSpy);
  });

  it('appel avec la bonne url', () => {
    httpClientSpy.get.and.returnValue(of(jasmine.any('IUser')));
    usersService.getUser(1).subscribe(result => {
      expect(httpClientSpy.get).toHaveBeenCalledWith('https://monSite.fr/api/users/1');
    });
  });
});

blog comments powered by Disqus