Les tests Angularjs : le guide de A à Z - Partie 1, les tests unitaires

⏲️ ~7 min de lecture

Publié le par Hadrien
Angularjs JavaScript tests

Angularjs est le framework front-end à la mode en ce moment, celui que tout le monde a eu envie d'essayer au moins une fois, juste pour voir.

Chez Occitech en tout cas, on a eu envie.

Et là c'est le drame #

Le framework étant tout neuf, son manque de documentation peut vite vous faire défaut. Surtout quand il s'agit de faire des tests.

Heureusement pour vous, on s'est cassé les dents à votre place, et comme nous sommes de braves gens, on va tout vous expliquer du début à la fin.

La première chose à savoir, c'est qu'Angularjs met deux types de tests à notre disposition : des tests unitaires, et des tests dits end to end, qui sont en fait des tests fonctionnels. Dans cette première partie, on va se concentrer sur les tests unitaires.

Mais avant de rentrer dans le détail des tests, commençons par installer les outils dont nous aurons besoin pour exécuter les tests.

Karma #

La principale chose à utiliser est karma (anciennement nommé testacular, on se demande pourquoi le nom a été changé…), qui a été créé spécialement pour les tests d'Angularjs au départ, mais qui est capable de tester n'importe quel type de projet javascript.

Installation #

Rien de plus simple grâce à notre ami npm :

$ npm install --save-dev karma

Ensuite, pour les tests unitaires, nous aurons besoin de jasmine :

$ npm install --save-dev karma-jasmine

Et enfin nous utiliserons PhantomJs comme navigateur exécutant les tests. Il faudra donc ajouter le launcher adapté pour Karma :

$ npm install --save-dev karma-phantomjs-launcher

Maintenant qu'on a karma et ses dépendances pour Angularjs, on va pouvoir entrer dans le vif du sujet et commencer nos tests !

Les tests unitaires #

Partons du code suivant :

// js/test_app.js
angular.module('testApp', [])
  .controller('FooController', ['$scope', '$http', '$location', function($scope, $http, $location) {
    $scope.model = {};

    $scope.save = function(model) {
    $http.post('/api/foo.json', model).success(function() {
      $location.url('/redirect/to/url');
    });
  };
}])
;

Quels sont les tests unitaires que nous pourrions appliquer au contrôleur FooController ? Il y en a trois qui sont évidents :

Empressons-nous de mettre ces tests en place !

Première étape : configuration de karma #

Eh oui, il faut d'abord dire à karma comment il doit être exécuté pour des tests unitaires Angularjs.

Rien de bien compliqué, avec la doc. du site officiel et angular-seed, on peut rapidement trouver comment faire :

// karma-unit.conf.js
module.exports = function(config) {
  config.set({
    basePath: './',
    files: [
      'bower_components/angular/angular.js',
      'bower_components/angular-mocks/angular-mocks.js',
      'js/test_app.js',
      'test/unit/**/*.js'
    ],
    frameworks: ['jasmine'],
    autoWatch: true,
    browsers: ['PhantomJS']
  });
};

Que fait-on ?

Écrire un premier test #

// test/unit/foo_controller.spec.js
describe('FooController', function() {
  var $scope, controller;

  beforeEach(module('testApp'));
  beforeEach(inject(function ($rootScope, $controller) {
    $scope = $rootScope.$new();
    controller = $controller('FooController', {
      $scope: $scope
    });
  }));

  it('should set save function', function() {
    expect($scope.save).toBeDefined();
  });
});

On ne va pas entrer en détail dans toute la partie beforeEach, ce serait trop long (reportez-vous à la doc. — haha — ou à différentes ressources sur le web qui vous expliqueront tout ça mieux que nous), et se focaliser sur notre test.

Jusque là, rien de bien compliqué, le test fait ce qu'il dit et vérifie que la variable $scope.save a bien été définie.

Lançons notre test :

$ node_modules/.bin/karma start karma-unit.conf.js --single-run
INFO [karma]: Karma v0.10.2 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.2 (Mac OS X)]: Connected on socket FUsDNFuXaM_LyVGxmJTs
PhantomJS 1.9.2 (Mac OS X): Executed 1 of 1 SUCCESS (0.09 secs / 0.01 secs)

Fantastique, c'est un succès !

Le second test #

Là, ça se corse un peu. On veut tester qu'une URL est bien appelée lors de l'appel à la fonction $scope.save(). Et pour ça, il n'y a pas trente-six solutions, il faut mocker !

La fonction inject() de la librairie de mocks d'Angularjs va nous permettre, comme son nom l'indique, d'injecter des modules mockés au sein de nos tests :

// test/unit/foo_controller.spec.js
describe('FooController', function() {
  //…
  it('should call /api/foo.json on $scope.save()', inject(function($httpBackend) {
    $scope.save();

    $httpBackend.expectPOST('/api/foo.json').respond();
    $httpBackend.flush();
  }));
});

Ici par exemple, on se sert du ngMock.$httpBackend, qui nous permet d'indiquer qu'un appel POST à '/api/foo.json' est attendu.

L'appel à respond() permet de simuler la réponse de notre mock. Puis on dit au mock $httpBackend de flusher les requêtes en attente (puisque normalement ce sont des requêtes asynchrones).

On lance nos tests :

node_modules/.bin/karma start karma-unit.conf.js --single-run
INFO [karma]: Karma v0.10.2 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.2 (Mac OS X)]: Connected on socket 3N5hoDfNmHw3UcqZnLRc
PhantomJS 1.9.2 (Mac OS X): Executed 2 of 2 SUCCESS (0.096 secs / 0.013 secs)

Encore une fois c'est un succès. :)

Le troisième et dernier test #

On doit maintenant vérifier que $location.url() est appelée en cas de succès de l'appel API et qu'il doit avoir pour paramètre '/redirect/to/url'.

Pour faire ça, rien de plus simple, on va utiliser les mocks de Jasmine.

// test/unit/foo_controller.spec.js
describe('FooController', function() {
  //…
  beforeEach(inject(function ($rootScope, $controller) {
    //…
    $location = jasmine.createSpyObj('$location', ['url']);
    controller = $controller('FooController', {
      $scope: $scope,
      $location: $location
    });
  }));
  //…
  it('should redirect to /redirect/to/url', inject(function($httpBackend) {
    $scope.save();
    $httpBackend.whenPOST('/api/foo.json').respond(200);
    $httpBackend.flush();

    expect($location.url).toHaveBeenCalledWith('/redirect/to/url');
  }));
});

Ce qu'on a rajouté :

On exécute les tests :

node_modules/.bin/karma start karma-unit.conf.js --single-run
INFO [karma]: Karma v0.10.2 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.2 (Mac OS X)]: Connected on socket 47n3Fbu5OR4m2oHsqdbB
PhantomJS 1.9.2 (Mac OS X): Executed 3 of 3 SUCCESS (0.098 secs / 0.013 secs)

Et voilà, tous les tests passent !

Conclusion #

Pour conclure, on peut dire que les tests Angularjs, c'est pas si compliqué que ça, mais le manque de documentation « officielle » est quand même un gros défaut. Et vouloir se lancer dedans tout seul peut parfois faire peur et n'est pas forcément évident non plus.

Par contre une fois qu'on a chopé le coup, on peut tout tester sans plus jamais avoir peur de rien !

Tester le code de ce billet #

Parce qu'à Occitech, on est vraiment des gars sympas, on vous a préparé un dépôt Github avec tout le code décrit ici prêt (ou presque) à être exécuté.

Pour ce faire, rendez-vous ici : https://github.com/occitech/angularjs-testing.

Cet article vous a plu ? Sachez que nous recrutons !

← Accueil