Test d'unité de requêtes AJAX avec QUnit

Nous essayons d'implémenter des tests Java QUnit pour une application web lourde JS. Nous nous efforçons de trouver un moyen de tester avec succès les méthodes impliquant les requêtes jQuery AJAX. Par exemple, nous avons la fonction constructeur suivante (évidemment, c'est un exemple très simpliste):

var X = function() { this.fire = function() { $.ajax("someURL.php", { data: { userId: "james" }, dataType: "json", success: function(data) { //Do stuff } }); }; }; var myX = new X(); myX.fire(); 

Nous essayons de trouver un moyen de tester la méthode du fire , de préférence avec une URL someURL.php au lieu du réel someURL.php .

La seule solution évidente pour le moment est d'ajouter l'URL et le rappel de success , en tant qu'arguments à la fonction constructeur. De cette façon, dans le test, nous pouvons créer une nouvelle instance de X et passer dans l'URL du stub, et un rappel pour l'exécution lorsque le talon renvoie une réponse. Par exemple:

 test("Test AJAX function", function() { stop(); var myX = new X(); //Call the AJAX function, passing in the stub URL and success callback myX.fire("stub.php", function(data) { console.log(data); start(); }); }); 

Cependant, cela ne semble pas être une solution très agréable. Y a-t-il une meilleure façon?

Avec jQuery, vous pouvez utiliser l'objet xhr que .ajax() renvoie comme une promesse, de sorte que vous pouvez ajouter plus de gestionnaires (voir ci-dessous) que les success , les error et les error que vous définissez dans les options. Donc, si votre fonction async peut renvoyer l'objet xhr, vous pouvez ajouter des gestionnaires spécifiques aux tests.

Quant à l'URL, c'est un peu plus délicat. J'ai parfois configuré un serveur Node très simple sur localhost, qui sert uniquement à des réponses en conserve qui ont été copiées à partir du serveur réel. Si vous exécutez votre suite de test sur ce même serveur, vos URL doivent simplement être des chemins absolus pour accéder au serveur de test au lieu du serveur de production. Et vous obtenez également un enregistrement des demandes elles-mêmes, comme un serveur les voit. Ou vous pouvez demander au serveur de test de renvoyer des erreurs ou de mauvaises réponses, si vous voulez voir comment le code le gère.

Mais c'est bien sûr une solution assez complexe. Le plus facile serait de définir vos URL dans un endroit où vous pouvez les redéfinir à partir de la suite de test. Par exemple:

 /* in your code */ var X = function () { this.fire = function () { return $.ajax({ url: this.constructor.url, ... }); }; }; X.url = "someURL.php"; // the production url /* in your tests */ X.url = "stub.php"; // redefine to the test url 

Aussi, QUnit a une fonction asyncTest , qui appelle stop() pour vous. Ajoutez une petite aide pour garder une trace de quand recommencer, et vous avez une très bonne solution.

Voici ce que j'ai fait avant

 // create a function that counts down to `start()` function createAsyncCounter(count) { count = count || 1; // count defaults to 1 return function () { --count || start(); }; } // .... // an async test that expects 2 assertions asyncTest("testing something asynchronous", 2, function() { var countDown = createAsyncCounter(1), // the number of async calls in this test x = new X; // A `done` callback is the same as adding a `success` handler // in the ajax options. It's called after the "real" success handler. // I'm assuming here, that `fire()` returns the xhr object x.fire().done(function(data, status, jqXHR) { ok(data.ok); equal(data.value, "foobar"); }).always(countDown); // call `countDown` regardless of success/error }); 

countDown est une fonction qui compte à zéro à partir de tout ce que vous spécifiez, puis appelle start() . Dans ce cas, il y a 1 appel asynchrone, donc countDown compte en compte. Et ce sera le cas lorsque l'appel ajax sera terminé, peu importe comment cela s'est produit, car il est configuré comme un rappel constant.
Et parce que l' asyncTest est appelé à attendre 2 assertions, il signalera une erreur si le .done() n'est jamais appelé, car aucune assertion ne sera exécutée. Donc, si l'appel échoue complètement, vous le saurez aussi. Si vous souhaitez enregistrer quelque chose en erreur, vous pouvez ajouter un rappel de .fail() à la chaîne de promesses.

Si c'est un test d'unité qui peut (et devrait) être exécuté isolément du côté du serveur, vous pouvez simplement "remplacer" $.ajax pour simuler tout comportement. Un exemple simple:

 test("Test AJAX function", function() { // keep the real $.ajax var _real_ajax = $.ajax; // Simulate a successful response $.ajax = function(url, opts) { opts.success({expected: 'response'}); } var myX = new X(); // Call your ajax function myX.fire(); // ... and perform your tests // Don't forgot to restore $.ajax! $.ajax = _real_ajax; }); 

De toute évidence, vous pouvez également effectuer un véritable appel ajax avec url / data stupéfié:

 // Simulate a successfully response $.ajax = function(url, opts) { opts.success = function(data) { console.log(data); start(); } _real_ajax('stub.php', opts) } 

Si vous n'avez pas une réponse complexe, je préfère la première approche, car elle est plus rapide et plus facile à comprendre.
Cependant, vous pouvez également prendre une autre solution et mettre la logique Ajax dans sa propre méthode, de sorte que vous pouvez facilement la débrouiller pendant les tests.

Essayez d'utiliser l' jQuery spy . J'ai créé un moyen plus simple d'utiliser la syntaxe familiale jQuery pour tester ajax.

Vérifiez ce lien