Hubvisory lance la première formation Product 100% en ligne (éligible CPF) ! Voir

Construire des tests unitaires sur un projet Angular-GraphQL

Par Myriam le 24/10/2021 dans Articles

Expertise

6 minutes

Tester son code est une pratique commune et indispensable lorsqu'on souhaite construire des produits dont le code est solide et pérenne. Cela permet également d'accroître notre confiance sur les fonctionnalités d'un produit avant une mise en production. Pour faire suite à l'article "Mettre en place un projet Angular avec GraphQL", nous allons voir ensemble comment écrire des tests unitaires sur cette même stack avec le framework Jest et la librairie AngularTesting Library.

Framework de test : Remplacer Karma par Jest

Angular est packagé par défaut avec le framework de tests Karma. Cependant, nous allons le remplacer par Jest. Les aficionados actuels de Jest sont déjà nombreux et ne sont plus à convaincre. La documentation et l’entraide de la communauté Jest est bien fournie. De plus, un des principaux avantages de Jest est sa rapidité pour faire tourner les tests.

Voici les étapes pour remplacer Karma et mettre en place Jest sur votre projet Angular.

Supprimer Karma, sa configuration et ses dépendances

Commençons par supprimer Karma et les dépendances associées à l’aide de la commande suivante :

npm remove @types/jasmine jasmine-core karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter

Supprimons également les fichiers de configuration associés à Karma :

rm src/karma.conf.js src/test.ts

La partie “test” dans le fichier angular.json peut également être supprimée.

Installation de Jest

Ensuite, installons Jest sur notre projet. Nous l’accompagnons de ts-jest (un tranformateur compatible Jest qui nous permettra d’écrire les tests en TypeScript), du module @types/jest pour la définition des types liés à Jest et de jest-preset-angular qui nous permettra d’avoir la pré-configuration requise pour utiliser Jest au sein d’un projet Angular.

npm install -D jest ts-jest @types/jest jest-preset-angular

Nous allons ensuite créer les 2 fichiers suivants de configuration pour charger le module jest-preset-angular dans la configuration Jest.

// setup-jest.ts

import 'jest-preset-angular/setup-jest';
// jest.config.js

module.exports = {
  preset: "jest-preset-angular",
  setupFilesAfterEnv: ["<rootDir>/setup-jest.ts"],
};

Nous allons également modifier le fichier de configuration typescript spécifique aux tests :

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "esModuleInterop": true,
    "emitDecoratorMetadata": true,
    "outDir": "./out-tsc/spec",
    "types": ["jest"]
  },
  "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
}

Pour finaliser la mise en place de Jest, dans package.json, nous pouvons remplacer le script “test” qui comportait la commande spécifique à Karma par celle de Jest, et ajouter un script pour faire tourner les tests en mode continu (= watch mode) :

"test": "jest",
"test:watch": "jest --watch",

Installation de Angular Testing Library

Pour compléter notre stack de testing, nous allons ajouter Angular Testing Library.

Cette librairie est plus documentée sur React que sur Angular, cependant, elle est très intéressante car elle permet de se concentrer sur des tests très proches du comportement de l’utilisateur final de notre application, tout en poussant à abstraire au maximum les détails d’implémentation du code.

Ceci permet d’avoir des tests plus facilement maintenables et de maximiser notre confiance sur le bon fonctionnement de notre application, notamment lors de tests d’intégration.

Voici le lien vers la documentation, et la commande d’installation :

npm install --save-dev @testing-library/angula

Tester un composant en créant un mock du service associé

Nous allons nous concentrer sur l’écriture d’un test du UserComponent. L’idée est de tester ce qu’il affiche à l’utilisateur.

Dans notre application, il s’agit du nom et prénom de l’utilisateur. Pour les tests, comme pour les composants et leurs services associés, nous respectons le principe de séparation des responsabilités. Notre test du UserComponent se concentre uniquement sur celui-ci et pour cela, nous allons “mocker” le service auquel il fait appel.

Voici la configuration pour le mock du service :

import { NetworkStatus } from '@apollo/client/core';
import { render, screen } from '@testing-library/angular';
import { of } from 'rxjs';
import { UserComponent } from './user.component';
import { UserService } from './user.service';

describe('UserComponent', () => {
  let mockedGetUserFromId = jest.fn().mockReturnValue(
    of({
      data: {
        user: {
          id: 1,
          firstName: 'John',
          lastName: 'Doe',
        },
      },
      loading: false,
      networkStatus: NetworkStatus.ready,
    })
  );
  let mockedUserService = {
    getUserFromId: mockedGetUserFromId,
  };

  it.todo('should display the firstname and lastname of the user');
  
});

Nous avons ainsi défini une “fonction mock” pour imiter le retour de la fonction getUserFromId du UserService. Dans le “véritable” service, la fonction retourne un Observable, donc nous faisons de même pour notre fonction mock.

Pour utiliser le UserComponent avec le mock de son service pour notre test, nous pouvons simplement passer un objet en 2e argument de la fonction render de Angular Testing Library. Et dans cet objet, nous précisons le provider à utiliser pour le rendu de ce composant, à savoir le service mocked créé précédemment.

Voici donc comment écrire le test du UserComponent à l’aide de Angular Testing Library :

it('should display the firstname and lastname of the user', async () => {
    await render(UserComponent, {
      componentProviders: [
        {
          provide: UserService,
          useValue: mockedUserService,
        },
      ],
    });
    expect(screen.getByText(/John/i)).toBeTruthy();
    expect(screen.getByText(/Doe/i)).toBeTruthy();
  });

Tester un service grâce au ApolloTestingModule & ApolloTestingController

Après avoir testé le composant lui-même, nous allons nous assurer de tester le service. Nous commençons par créer un fichier user.service.spec.ts. Pour rappel, notre service utilise Apollo pour faire des requêtes GraphQL à notre backend, donc nous configurons le “cadre” de test en faisant appel au ApolloModuleTesting auquel nous injectons notre UserService.

//user.service.spec.ts

import { TestBed } from '@angular/core/testing';
import {
  ApolloTestingModule,
  ApolloTestingController,
} from 'apollo-angular/testing';

import { UserService, GET_USER_QUERY } from './user.service';

describe('UserService', () => {
  let service: UserService;
  let controller: ApolloTestingController;

  describe('GetUserFromId', () => {
    beforeEach(() => {
      TestBed.configureTestingModule({
        imports: [ApolloTestingModule],
      });
      service = TestBed.inject(UserService);
      controller = TestBed.inject(ApolloTestingController);
    });

    it.todo('should return the correct user info from id');
  });
});

Ensuite, pour tester plus spécifiquement la fonction getUserFromId de notre UserService, nous voulons lui préciser certaines données d’entrées, et vérifier ses données de sortie. Nous injectons donc également le service ApolloTestingController qui va permettre d’imiter les appels au point d’accès GraphQL. Cela a pour avantage de réduire les dépendances en isolant notre test des données distantes et nous pouvons définir les données de retour, ceci garantit que le test soit reproductible.

Grâce à expectOne nous vérifions au passage que la bonne query graphQL a été utilisée par la fonction.

Dans la méthode flush, on détermine l’objet retourné par notre service pour ce test.

Voici le détail du test :

it('should return the correct user info from id', (done) => {
      service.getUserFromId('1').subscribe((response) => {
        expect(response.data.user).toEqual({
          id: '1',
          firstName: 'John',
          lastName: 'Doe',
        });
        done();
      });

      const op = controller.expectOne(GET_USER_QUERY);

      // Respond with mock data, causing Observable to resolve.
      op.flush({
        data: {
          user: {
            id: '1',
            firstName: 'John',
            lastName: 'Doe',
          },
        },
      });

      // Finally, assert that there are no outstanding operations.
      controller.verify();
    });

La partie testing de la documentation d’apollo-angular donne plus de précisions si nécessaire.

A partir de notre stack Angular-GraphQL, nous avons personnalisé les technologies de testing en remplaçant Karma par Jest, et en ajoutant Angular Testing Library. Une fois cette configuration mise en place, nous avons pu écrire des tests unitaires simples sur un composant et son service avec les mocks nécessaires à l’isolation de chaque test.

Voici le lien vers l’application complète avec tous les fichiers.

Partagez l'article avec votre réseau !

Card image cap
Myriam

Développeuse