Comment vérifier les événements jQuery AJAX avec Jasmine?


114

J'essaie d'utiliser Jasmine pour écrire des spécifications BDD pour les requêtes jQuery AJAX de base. J'utilise actuellement Jasmine en mode autonome (c'est-à-dire via SpecRunner.html). J'ai configuré SpecRunner pour charger jquery et d'autres fichiers .js. Des idées pourquoi ce qui suit ne fonctionne pas? has_returned ne devient pas vrai, même pensait le "yuppi!" l'alerte s'affiche bien.

describe("A jQuery ajax request should be able to fetch...", function() {

  it("an XML file from the filesystem", function() {
    $.ajax_get_xml_request = { has_returned : false };  
    // initiating the AJAX request
    $.ajax({ type: "GET", url: "addressbook_files/addressbookxml.xml", dataType: "xml",
             success: function(xml) { alert("yuppi!"); $.ajax_get_xml_request.has_returned = true; } }); 
    // waiting for has_returned to become true (timeout: 3s)
    waitsFor(function() { $.ajax_get_xml_request.has_returned; }, "the JQuery AJAX GET to return", 3000);
    // TODO: other tests might check size of XML file, whether it is valid XML
    expect($.ajax_get_xml_request.has_returned).toEqual(true);
  }); 

});

Comment tester que le rappel a été appelé? Tout pointeur vers des blogs / matériel lié au test de jQuery asynchrone avec Jasmine sera grandement apprécié.

Réponses:


234

Je suppose qu'il existe deux types de tests que vous pouvez faire:

  1. Des tests unitaires qui simulent la requête AJAX (en utilisant les espions de Jasmine), vous permettant de tester tout votre code qui s'exécute juste avant la requête AJAX, et juste après . Vous pouvez même utiliser Jasmine pour simuler une réponse du serveur. Ces tests seraient plus rapides - et ils n'auraient pas besoin de gérer un comportement asynchrone - car il n'y a pas de véritable AJAX en cours.
  2. Des tests d'intégration qui exécutent de vraies requêtes AJAX. Celles-ci devraient être asynchrones.

Jasmine peut vous aider à faire les deux types de tests.

Voici un exemple de la façon dont vous pouvez simuler la demande AJAX, puis écrire un test unitaire pour vérifier que la fausse demande AJAX allait à l'URL correcte:

it("should make an AJAX request to the correct URL", function() {
    spyOn($, "ajax");
    getProduct(123);
    expect($.ajax.mostRecentCall.args[0]["url"]).toEqual("/products/123");
});

function getProduct(id) {
    $.ajax({
        type: "GET",
        url: "/products/" + id,
        contentType: "application/json; charset=utf-8",
        dataType: "json"
    });
}

Pour Jasmine 2.0, utilisez à la place:

expect($.ajax.calls.mostRecent().args[0]["url"]).toEqual("/products/123");

comme indiqué dans cette réponse

Voici un test unitaire similaire qui vérifie que votre rappel a été exécuté, sur une requête AJAX réussie:

it("should execute the callback function on success", function () {
    spyOn($, "ajax").andCallFake(function(options) {
        options.success();
    });
    var callback = jasmine.createSpy();
    getProduct(123, callback);
    expect(callback).toHaveBeenCalled();
});

function getProduct(id, callback) {
    $.ajax({
        type: "GET",
        url: "/products/" + id,
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: callback
    });
}

Pour Jasmine 2.0, utilisez à la place:

spyOn($, "ajax").and.callFake(function(options) {

comme indiqué dans cette réponse

Enfin, vous avez laissé entendre ailleurs que vous pourriez vouloir écrire des tests d'intégration qui font de vraies requêtes AJAX - à des fins d'intégration. Cela peut être fait en utilisant les fonctionnalités asynchrones de Jasmine: waits (), waitsFor () et runs ():

it("should make a real AJAX request", function () {
    var callback = jasmine.createSpy();
    getProduct(123, callback);
    waitsFor(function() {
        return callback.callCount > 0;
    });
    runs(function() {
        expect(callback).toHaveBeenCalled();
    });
});

function getProduct(id, callback) {
    $.ajax({
        type: "GET",
        url: "data.json",
        contentType: "application/json; charset=utf-8"
        dataType: "json",
        success: callback
    });
}

+1 Alex grande réponse. En fait, j'ai rencontré un problème similaire pour lequel j'ai ouvert une question Test Ajax request en utilisant l'objet Deferred . Pouvez-vous jeter un oeil? Merci.
Lorraine Bernard

12
J'aimerais vraiment que votre réponse fasse partie de la documentation officielle de Jasmine. Réponse très claire à un problème qui me tue depuis quelques jours.
Darren Newton

4
Cette réponse devrait vraiment être marquée comme la réponse officielle à cette question.
Thomas Amar

1
La méthode actuelle pour obtenir les informations les plus récentes sur les appels est $ .ajax.calls.mostRecent ()
camiblanch

1
Comment implémenteriez-vous cela pour un ajax JS ordinaire?
Horloger

13

Regardez le projet jasmine-ajax: http://github.com/pivotal/jasmine-ajax .

C'est une aide de remplacement qui (pour jQuery ou Prototype.js) stubs au niveau de la couche XHR afin que les requêtes ne sortent jamais. Vous pouvez alors vous attendre à tout ce que vous voulez sur la demande.

Ensuite, il vous permet de fournir des réponses fixes pour tous vos cas, puis d'écrire des tests pour chaque réponse que vous souhaitez: succès, échec, non autorisé, etc.

Il sort les appels Ajax du domaine des tests asynchrones et vous offre beaucoup de flexibilité pour tester le fonctionnement de vos gestionnaires de réponses réels.


Merci @jasminebdd, le projet jasmine-ajax ressemble à la voie à suivre pour tester mon code js. Mais que se passe-t-il si je voulais tester avec des requêtes réelles au serveur, par exemple pour des tests de connectivité / d'intégration?
mnacos

2
@mnacos jasmine-ajax est surtout utile pour les tests unitaires, auquel cas vous souhaitez spécifiquement éviter l'appel au serveur. Si vous effectuez des tests d'intégration, ce n'est probablement pas ce que vous voulez et devrait être conçu comme une stratégie de test distincte.
Sebastien Martin

7

voici un exemple simple de suite de tests pour une application js comme celle-ci

var app = {
               fire: function(url, sfn, efn) {
                   $.ajax({
                       url:url,
                       success:sfn,
                       error:efn
                   });
                }
         };

un exemple de suite de tests, qui appellera un rappel basé sur une expression régulière d'url

describe("ajax calls returns", function() {
 var successFn, errorFn;
 beforeEach(function () {
    successFn = jasmine.createSpy("successFn");
    errorFn = jasmine.createSpy("errorFn");
    jQuery.ajax = spyOn(jQuery, "ajax").andCallFake(
      function (options) {
          if(/.*success.*/.test(options.url)) {
              options.success();
          } else {
              options.error();
          }
      }
    );
 });

 it("success", function () {
     app.fire("success/url", successFn, errorFn);
     expect(successFn).toHaveBeenCalled();
 });

 it("error response", function () {
     app.fire("error/url", successFn, errorFn);
     expect(errorFn).toHaveBeenCalled();
 });
});

Merci mec. Cet exemple m'a beaucoup aidé! Notez simplement que si vous utilisez Jasmine> 2.0, la syntaxe de andCallFake est spyOn (jQuery, "ajax"). And.callFake (/ * votre fonction * /);
João Paulo Motta

Je ne sais pas s'il s'agit d'un problème de version, mais j'ai eu une erreur .andCallFake, utilisée à la .and.callFakeplace. Merci.
OO

5

Lorsque je spécifie du code ajax avec Jasmine, je résous le problème en espionnant la fonction dépendante qui lance l'appel distant (comme, par exemple, $ .get ou $ ajax). Ensuite, je récupère les rappels définis dessus et je les teste discrètement.

Voici un exemple que j'ai donné récemment:

https://gist.github.com/946704


0

Essayez jqueryspy.com Il fournit une élégante syntaxe de type jquery pour décrire vos tests et permet aux rappels de tester une fois que l'ajax est terminé. C'est idéal pour les tests d'intégration et vous pouvez configurer les temps d'attente maximum ajax en secondes ou en millièmes de seconde.


0

J'ai l'impression que j'ai besoin de fournir une réponse plus à jour puisque Jasmine est maintenant à la version 2.4 et que quelques fonctions ont changé depuis la version 2.0.

Donc, pour vérifier qu'une fonction de rappel a été appelée dans votre requête AJAX, vous devez créer un espion, lui ajouter une fonction callFake puis utiliser l'espion comme fonction de rappel. Voici comment ça se passe:

describe("when you make a jQuery AJAX request", function()
{
    it("should get the content of an XML file", function(done)
    {
        var success = jasmine.createSpy('success');
        var error = jasmine.createSpy('error');

        success.and.callFake(function(xml_content)
        {
            expect(success).toHaveBeenCalled();

            // you can even do more tests with xml_content which is
            // the data returned by the success function of your AJAX call

            done(); // we're done, Jasmine can run the specs now
        });

        error.and.callFake(function()
        {
            // this will fail since success has not been called
            expect(success).toHaveBeenCalled();

            // If you are happy about the fact that error has been called,
            // don't make it fail by using expect(error).toHaveBeenCalled();

            done(); // we're done
        });

        jQuery.ajax({
            type : "GET",
            url : "addressbook_files/addressbookxml.xml",
            dataType : "xml",
            success : success,
            error : error
        });
    });
});

J'ai fait le truc pour la fonction de réussite ainsi que la fonction d'erreur pour m'assurer que Jasmine exécutera les spécifications dès que possible, même si votre AJAX renvoie une erreur.

Si vous ne spécifiez pas de fonction d'erreur et que votre AJAX renvoie une erreur, vous devrez attendre 5 secondes (intervalle de temporisation par défaut) jusqu'à ce que Jasmine génère une erreur Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.. Vous pouvez également spécifier votre propre délai comme ceci:

it("should get the content of an XML file", function(done)
{
    // your code
},
10000); // 10 seconds
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.