Tests unitaires avec Jasmine pour AngularJS

Lors des précédents articles, nous avons construits une application AngularJS qui effectue quelques calculs. Pour s’assurer que les fonctions en charge des calculs restent valide quand l’application évolue, les tests unitaires sont nécessaires. Comment s’y prendre dans l’écosystème AngularJS ?

Ce qui m’a le plus déstabilisé quand j’ai abordé ce sujet réside dans la compréhension des outils utilisés.

Il y a pléthore de tutoriels mais peu expliquent a quoi servent chaque utilitaire. Nous allons donc découvrir comment effectuer un test unitaire avec Jasmine pour AngularJS.

Les outils

Nous allons découvrir les deux outils nécessaires pour effectuer les tests.

Karma

A simple tool that allows you to execute JavaScript code in multiple real browsers.

The main purpose of Karma is to make your test-driven development easy, fast, and fun.

Tiré de npm

Karma permet d’exécuter du code JavaScript dans plusieurs browsers. Il est possible de sélectionner un ou plusieurs browsers lors de l’installation des outils.

Ensuite il faut choisir la bibliothèque/framework de test souhaité. Jasmine, QUnit, Mocha. Pour faciliter la découverte, j’ai utilisé Jasmine. Mais c’est quoi exactement ?

Jasmine

Il s’agit d’un framework de behavior driven development pour tester du code JavaScript.

Deux avantages de ce framework :

1 – il est indépendant d’autres frameworks JS

2 – il ne nécessite pas un DOM

De plus la syntaxe est lisible et rapidement appréhendable, comme nous le verrons plus loin.

Installation et configuration

Maintenant que nous avons découvert les outils, nous allons les installer et les configurer. En prérequis, il est nécessaire avoir installé nodejs et bower.

# ouvrir la console console npm
# se placer dans le répertoire du projet
cd C:UsersGhislainDocumentsGitHubng-SealedSpaceTimer

# installer karma
npm install karma --save-dev

# choisir quel "browser" utiliser.
# du classique Chrome, Firefox à des outils comme PhantomJS ou BrowserStack
# http://karma-runner.github.io/0.12/config/browsers.html
npm install karma-chrome-launcher

# installer jasmine
npm install karma-jasmine --save-dev

# faciliter l'appel a karma en ligne de commande
npm install -g karma-cli

# installer le mock pour angular afin de tester
bower install angular-mocks save-dev

# créer le fichier karma.conf.js.
# répondre aux questions pour compléter le fichier
karma init karma.conf.js

# lancer les tests, si la surveillance des modifications est activée,
# alors à chaque modification les tests seront exécutés
karma start karma.conf.js
# lancer les tests une seule fois
karma start karma.conf.js --single-run

Ecrire les tests

Description

Jasmine utilise une syntaxe basée sur l’idée d’expectation. Les tests sont regroupées dans des zones describe. Chaque test début par it suivi du message de description du test puis le contenu du test. Le test contient une ou plusieurs expectations décrites par expect. Une expectation utilise un matcher (cf le paragraphe dédié) qui matérialise la condition à valider.

describe('section des tests de la fonctionnalité F1', function() {
   it('devrait etre egal a 1 dans le cas d’usage U2',
      inject(function($controller){
         // initialiser les variables pour le cas U2
         expect($resultat).toBe(1);
      }
));

Jasmine permet de tester les contrôleurs, les directives, les filtres, les services, les factory. Je n’ai pour l’instant testé sur les contrôleurs mais théorique Jasmine permet de tester tous les concepts d’AngularJS.

Les Matchers

Les matchers constituent les mécanismes de base pour valider les expectations d’un test. Nativement Jasmine propose un bon nombre de possibilité. L’avantage réside dans le fait qu’il est possible de créer ses propres matchers pour des besoins spécifiques.

http://jasmine.github.io/2.0/custom_matcher.html

expect(fn).toThrow(e);
 expect(instance).toBe(instance);
 expect(mixed).toBeDefined();
 expect(mixed).toBeFalsy();
 expect(number).toBeGreaterThan(number);
 expect(number).toBeLessThan(number);
 expect(mixed).toBeNull();
 expect(mixed).toBeTruthy();
 expect(mixed).toBeUndefined();
 expect(array).toContain(member);
 expect(string).toContain(substring);
 expect(mixed).toEqual(mixed);
 expect(mixed).toMatch(pattern);

source : https://github.com/JamieMason/Jasmine-Matchers

Quelques erreurs

Lors de l’élaboration des tests unitaires avec Jasmine pour AngularJS, j’ai essuyé quelques plâtres.

ERROR Uncaught ReferenceError: angular is not defined

=> dans karma.conf.js s’assurer que angular.js est chargé avant l’appel à angular.

module undefined

=> dans karma.conf.js charger tous les modules de l’application dans le beforeEach. Si on se contente de ceux du contrôleur, cela va échouer.

Error: [ng:areq] Argument ‘HomeController’ is not a function, got undefined

=> le fichier JS définissant le contrôleur est-il bien chargé ? Vérifier les lignes d’inclusion dans le fichier karma.config.js

Exemple test d’un Controlleur

describe('HomeCtrl', function() {
   var $scope, $controller;
   // chargement des modules avant chaque test
   beforeEach(function(){
      module('MyApp');
      module('timer');
      module('ngRoute');
      module('ui.bootstrap');
      module('pascalprecht.translate');
   });

   // création du contexte avant chaque test
   beforeEach(inject(function($rootScope, _$controller_){
      $scope = $rootScope.$new();
      $controller = _$controller_;

      $controller('HomeCtrl', {'$scope': $scope});
   }));

   // extrait de la zone des tests des volumes
   describe('Volume', function() {
      it('compute rect volume 1', inject(function($controller){
         $scope.shape = 'rectangle';
         $scope.hauteur = 1;
         $scope.largeur = 1;
         $scope.longueur = 1;

         $vol = $scope.computeVolume();
         expect($vol).toBe(1);
      }));
  });

  // extrait de la zone des tests du temps restant
  describe('Time left', function() {
      it('compute time left in a rect volume 1m3 for 2 person',
         inject(function($controller){
            $scope.shape = 'rectangle';
            $scope.hauteur = 1;
            $scope.largeur = 1;
            $scope.longueur = 1;
            $scope.nbPersonne = 2;

            $timeLeft = $scope.computeTimeLeft();
            expect($timeLeft).toBeCloseTo(2700);
         }
      ));
   });
});


Invite de commande de l’exécution d’un test unitaire

Nous avons débroussaillé les tests unitaires avec Jasmine pour AngularJS. Installation et configuration de l’environnement, description des tests Jasmine et écriture d’un test pour un contrôleur. Jasmine propose encore beaucoup de fonctionnalités. Mais la prochaine étape sera d’aborder les test e2e (end to end) qui permettent d’effectuer des tests fonctionnels pour simuler le comportement d’un utilisateur.

Références:

Test unitaire – site officiel AngularJS

Karma – site officiel

Jasmine – site officiel