I. Préparation

Avant de commencer ce tutoriel, vous aurez besoin de ce qui suit :

  • alexyoung/dailyjs-backbone-tutorial en version 9691fc1 ;
  • la clé de l'API de la partie 2 ;
  • l'ID client de la partie 2 ;
  • la mise à jour de app/js/config.js avec vos clés.

Pour consulter la source, exécutez les commandes suivantes (ou utilisez un outil approprié pour Git GUI) :

 
Sélectionnez
git clone git@github.com:alexyoung/dailyjs-backbone-tutorial.git
cd dailyjs-backbone-tutorial
git reset --hard 9691fc1

II. Test de la méthode Backbone.sync personnalisée

Le but de ce tutoriel est de montrer comment écrire des tests qui n'ont pas besoin d'utiliser les API directement. La façon dont cela se fait habituellement passe par une technique appelée mocking. Lorsque l'application essaie de parler à l'API, elle doit communiquer avec un objet spécial que nous pouvons contrôler.

Lorsque vous testez des applications qui tournent autour d'une implémentation personnalisée de Backbone.sync, vous devez séparer proprement le test des applications de tests de l'API. Nous voulons tester ce dont nous sommes responsables et exécuter des tests sans connexion Internet ! Si vous regardez la façon dont app/js/gapi.js fonctionne, elle s'appuie sur gapi.client qui est fourni par Google. Il s'agit d'une cible facile pour faire du mocking. Nous pouvons remplacer la bibliothèque de Google avec quelque chose qui retourne des échantillons de données à la place.

Sinon.JSSinon.JS fait tout cela et plus encore. Sinon rend facile l'« espionnage » sur les méthodes qui résultent normalement lors des tentatives de connexion au serveur distant et ces espions peuvent facilement être branchés de manière appropriée.

L'approche de base que j'emploie pour des applications de tests pour Backbone avec Sinon,JS est la suivante :

  • créer des espions pour les opérations CRUD ;
  • scripter le DOM pour déclencher les choses que je veux tester ;
  • s'assurer que les espions ont vu les appels attendus ;
  • vérifier que l'interface utilisateur a été mise à jour en conséquence.

Voici un exemple dans notre application :

  • l'utilisateur clique sur « Ajouter une liste » et entre un titre de liste ;
  • le formulaire est envoyé ;
  • vérifier que gapi.client.tasks.tasklists a été appelée pour insérer la nouvelle liste ;
  • vérifier qu'un nouvel élément de liste a été ajouté à l'interface utilisateur.

Dans Mocha/Sinon, cela peut être exprimé ainsi :

 
Sélectionnez
suite('Lists', function() {
  var spyUpdate = sinon.spy(gapi.client.tasks.tasklists, 'update')
    , spyCreate = sinon.spy(gapi.client.tasks.tasklists, 'insert')
    , spyDelete = sinon.spy(gapi.client.tasks.tasklists, 'delete')
    ;

  setup(function() {
    spyUpdate.reset();
    spyCreate.reset();
  });

  test('Creating a list', function() {
    // TODO: faire les manipulations du DOM pour créer une liste
    assert.equal(1, spyCreate.callCount);
  });

  test('Editing a list', function() {
    // TODO: faire les manipulations du DOM pour modifier une liste
    assert.equal(1, spyUpdate.callCount);
  });

  // Example: Abstraction for testing
  test('Deleting a list', function() {
    // TODO: faire les manipulations du DOM pour supprimer une liste
  });
});

Enregistrez ceci en tant que test/lists.test.js et ajoutez lists.test.js comme une balise script à test/index.html (après app.test.js).

Les espionsspies de Sinon,JS sont créés avec sinon.spy(). Dans cet exemple, j'ai fourni deux arguments à sinon.spy, un objet et une méthode. Cela fait en sorte que Sinon,JS remplace la méthode avec une version qui peut compter le nombre de fois qu'elle est appelée. Elle se comporte comme la méthode d'origine, mais facilite les tests.

Les espions peuvent être appelés par d'autres moyens, mais dans ce tutoriel je vais me contenter de mettre l'accent sur ce modèle particulier. En général, c'est la partie de Sinon,JS que j'utilise le plus lorsqu'il s'agit d'applications de test pour Backbone.

III. Un peu de ménage

Dans ma quête pour garder ces tutoriels clairs et simples, j'ai remarqué que j'ai apporté quelques modifications à l'application qui font que les tests se rompent. Cela signifie que vous devrez faire un peu de ménage pour obtenir des tests qui fonctionnent correctement.

Tout d'abord, laissez les vues d'application se charger dans un conteneur au lieu de remplacer l'ensemble du body. Mocha a besoin d'une div pour afficher les résultats des tests et la dernière version de l'application force son remplacement lorsque les tests sont joués.

Ajoutez une nouvelle div à app/index.html et à test/index.html juste après la balise ouvrant du body :

 
Sélectionnez
<div id="todo-app"></div>

Vous pouvez masquer cette div dans les tests si vous le souhaitez. Mettre display: none ne cassera rien. Ce changement exige également que le fichier app/js/views/app.js soit mis à jour pour utiliser #todo-app au lieu du body pour la propriété el (vers le haut du fichier).

Vous devez également ajouter de nouvelles balises script au fichier test/index.html :

 
Sélectionnez
<script src="lib/sinon.js"></script>
<script src="fixtures/gapi.js"></script>

Il y a une chose qui reste à faire dans le fichier test/index.html. Il faut changer la façon dont les tests de Mocha sont appelés dans le bas du fichier :

 
Sélectionnez
<script>
  // Exécuter les tests uniquement lorsque l'application est prête
  require(['app'], function() {
    bTask.apiManager.on('ready', function() { mocha.run(); });
  });
</script>

Il s'agit d'un bon moyen de s'assurer que les tests s'exécutent uniquement lorsque les dépendances de l'application sont toutes chargées et ont été évaluées.

IV. Télécharger Sinon.JS

Il s'agit de la version de Sinon.JS que j'ai utilisé : sinon-1.5.2.jssinon-1.5.2.js. Enregistrez-le sous test/lib et créez le répertoire lib/ s'il n'existe pas déjà.

V. Mocking Google's API

Pour faire du mocking sur les API Google, j'ai simplement écrasé la bibliothèque fournie avec mon propre objet qui gère les callbacks avec les données de test appropriées. J'ai créé ces données de tests en utilisant l'application et en regardant l'onglet réseau de WebKit Inspector, donc il est basé sur un sous-ensemble de mes listes de tâches et tâches actuelles.

Vous pouvez utiliser mes données de test si vous voulez suivre ce tutoriel au lieu de vérifier la source complète de GitHub : alexyoung/4730178 (gapi.js).

Enregistrez cela en tant que test/fixtures/gapi.js. Créez le répertoire si nécessaire.

Maintenant lorsque app/js/gapi.js appelle gapi.client.load et s'authentifie avec OAuth2, il reçoit des fausses données utilisateurs. J'ai utilisé des structures de données similaires aux valeurs réelles, mais j'ai supprimé quelques choses comme pour le rendre plus facile à lire.

VI. Tests de listes

Maintenant test/lists.test.js peut être finalisé. Voici une façon de vérifier que les listes sont créées correctement :

 
Sélectionnez
test('Créer une liste', function() {
  var $el = bTask.views.app.$el
    , listName = 'Example list';

  // Montrer le formulaire d'ajout de liste
  $el.find('#add-list-button').click();

  // Remplir le champ titre
  $el.find('#list_title').val(listName);
  $el.find('#list_title').parents('form').first().submit();

  // S'assurer que l'espion a vu un appel de création de liste
  assert.equal(1, spyCreate.callCount);

  // S'assurer que l'élément UI attendu a été ajouté
  assert.equal(listName, $('.list-menu-item:last').text().trim());
});

Ce test utilise jQuery pour cliquer sur le bouton qui ouvre le formulaire d'ajout/de modification de liste, puis remplit un titre et envoie ensuite le formulaire. Ce qui provoque l'exécution de Backbone.sync et l'appel d'une opération d'insertion de l'API de Google. Parce que j'ai remplacé gapi, il appellera alors la version améliorée. Et puisque Sinon l'espionne, nous aurons un nombre incrémenté appel. Boum !

Modifier une liste est pratiquement la même chose :

 
Sélectionnez
test('Modifier une liste', function() {
  var $el = bTask.views.app.$el;

  // Montrer le formulaire de modification de liste
  $el.find('.list-menu-item:first').click();
  $el.find('#edit-list-button').click();

  $el.find('#list_title').val('Edited list');
  $el.find('#list_title').parents('form').first().submit();

  assert.equal(1, spyUpdate.callCount);
  assert.equal('Edited list', $('.list-menu-item:first').text().trim());
});

Cette fois-ci une Vincent VIALE2013-02-19T10:21:54.70Il me semble que c'est féminin : Le Dictionnaireupdate de l'API se déclenche au lieu d'un insert.

Il y a une chose curieuse dans ces essais : la suppression manuelle. J'ai utilisé une boîte de dialogue de confirmation (confirm) au lieu d'un widget moderne, ce qui signifie que les tests provoqueront l'apparition d'une boîte de dialogue confirm. Il est possible d'empêcher cette apparition en remplaçant la boîte de dialogue :

 
Sélectionnez
test('Supprimer une liste', function() {
  var $el = bTask.views.app.$el;

  // Confirmer automatiquement
  window.confirm = function() { return true; };

  // Montrer le formulaire de modification de liste
  $el.find('#edit-list-button').click();

  // Cliquer sur le bouton de suppression de liste
  $el.find('.delete-list').click();

  assert.equal(1, spyDelete.callCount);
});

Le problème avec ceci est que Mocha va voir une « fuite globale ». Vous pouvez ajouter confirm au fichier test/setup.js afin d'éviter cette erreur. Cependant, idéalement nous ne devrions pas avoir besoin de cela, coupler les tests avec les détails de l'implémentation est généralement une mauvaise idée. Il serait préférable de concevoir l'application de manière à permettre la désactivation de la boîte de dialogue afin d'obtenir un meilleur support de test. Je ne crains habituellement pas d'adapter les applications pour les rendre plus faciles à tester, cela peut être bénéfique à long terme.

Pour exécuter ces tests, lancer l'application Node avec npm start et puis visitez la page http://localhost:8080/test/ dans votre navigateur. Veillez à taper correctement l'URL ou les tests ne fonctionneront pas en raison de l'utilisation de chemins d'accès relatifs.

VII. Résumé

Dans cette partie, vous avez vu comment écrire des tests pour une application basée autour d'une implémentation personnalisée de Backbone.sync. Sinon spies constituent la manière idéale pour envelopper les parties internes d'une application Backbone pour écrire des suites de tests succincts.

Ce type particulier de test se concentre sur la logique métier représentée par les modèles, les collections et les vues Backbone. En fin de compte, cela contribue à maintenir facilement vos applications côté client malgré qu'elles grandissent et changent avec le temps.

Le code source de ce tutoriel se trouve ici : alexyoung / dailyjs-backbone-tutorial en version 45dd59.

VIII. Remerciements

Cet article a été publié avec l'aimable autorisation de Alex Young. L'article original peut être lu sur le site DailyJSDailyJS : Backbone.js Tutorial: Spies, Stubs et MocksBackbone.js Tutorial: Spies, Stubs, et Mocks.
Je remercie également zoom61 pour sa relecture attentive et assidue.