Come posso verificare gli eventi jQuery AJAX con Jasmine?


114

Sto cercando di utilizzare Jasmine per scrivere alcune specifiche BDD per richieste jQuery AJAX di base. Attualmente sto usando Jasmine in modalità standalone (cioè attraverso SpecRunner.html). Ho configurato SpecRunner per caricare jquery e altri file .js. Qualche idea sul perché quanto segue non funziona? has_returned non diventa vero, anche se il "yuppi!" l'avviso si presenta bene.

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);
  }); 

});

Come si verifica che la richiamata sia stata chiamata? Eventuali riferimenti a blog / materiale relativi al test di jQuery asincrono con Jasmine saranno molto apprezzati.

Risposte:


234

Immagino che ci siano due tipi di test che puoi fare:

  1. Test unitari che simulano la richiesta AJAX (utilizzando le spie di Jasmine), consentendo di testare tutto il codice che viene eseguito appena prima della richiesta AJAX e subito dopo . Puoi persino usare Jasmine per simulare una risposta dal server. Questi test sarebbero più veloci e non avrebbero bisogno di gestire un comportamento asincrono, poiché non è in corso alcun AJAX reale.
  2. Test di integrazione che eseguono richieste AJAX reali. Questi dovrebbero essere asincroni.

Jasmine può aiutarti a fare entrambi i tipi di test.

Di seguito è riportato un esempio di come è possibile falsificare la richiesta AJAX e quindi scrivere uno unit test per verificare che la richiesta AJAX falsificata andasse all'URL corretto:

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"
    });
}

Per Jasmine 2.0 utilizzare invece:

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

come notato in questa risposta

Ecco uno unit test simile che verifica che la tua richiamata sia stata eseguita, su una richiesta AJAX completata con successo:

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
    });
}

Per Jasmine 2.0 utilizzare invece:

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

come notato in questa risposta

Infine, hai accennato altrove che potresti voler scrivere test di integrazione che effettuano richieste AJAX reali, per scopi di integrazione. Questo può essere fatto usando le funzionalità asincrone di Jasmine: waits (), waitsFor () e 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 ottima risposta. In realtà ho riscontrato un problema simile per il quale ho aperto una domanda Test Ajax request utilizzando l'oggetto Deferred . Potresti dare un'occhiata? Grazie.
Lorraine Bernard

12
Vorrei davvero che la tua risposta facesse parte della documentazione ufficiale di Jasmine. Risposta molto chiara a un problema che mi sta uccidendo da alcuni giorni.
Darren Newton,

4
Questa risposta dovrebbe davvero essere contrassegnata come la risposta ufficiale a questa domanda.
Thomas Amar

1
Il modo corrente per ottenere le informazioni sulla chiamata più recenti è $ .ajax.calls.mostRecent ()
camiblanch

1
Come lo implementeresti per il semplice JS ajax?
Orologiaio

13

Guarda il progetto jasmine-ajax: http://github.com/pivotal/jasmine-ajax .

È un helper drop-in che (per jQuery o Prototype.js) si stub al livello XHR in modo che le richieste non vengano mai eseguite. Puoi quindi aspettarti tutto ciò che desideri dalla richiesta.

Quindi ti consente di fornire risposte fisse per tutti i tuoi casi e quindi scrivere test per ogni risposta che desideri: successo, fallimento, non autorizzato, ecc.

Prende le chiamate Ajax fuori dal regno dei test asincroni e fornisce molta flessibilità per testare come dovrebbero funzionare i gestori delle risposte effettive.


Grazie @jasminebdd, il progetto jasmine-ajax sembra la strada da percorrere per testare il mio codice js. Ma cosa succede se volessi testare con richieste effettive al server, ad esempio per test di connettività / integrazione?
mnacos

2
@mnacos jasmine-ajax è principalmente utile per i test di unità, nel qual caso si desidera specificamente evitare la chiamata al server. Se stai facendo test di integrazione, questo probabilmente non è quello che vuoi e dovrebbe essere progettato come una strategia di test separata.
Sebastien Martin

7

ecco un semplice esempio di test suite per un'app js come questa

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

una suite di test di esempio, che richiamerà la richiamata in base all'espressione regolare 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();
 });
});

Grazie amico. Questo esempio mi ha aiutato molto! Nota solo che se stai usando Jasmine> 2.0 la sintassi per andCallFake è spyOn (jQuery, "ajax") e.callFake (/ * tua funzione * /);
João Paulo Motta

Non sono sicuro che si tratti di un problema di versione, ma ho ricevuto un errore .andCallFake, utilizzato .and.callFakeinvece. Grazie.
OO

5

Quando specifico il codice ajax con Jasmine, risolvo il problema spiando qualsiasi funzione dipendente che avvia la chiamata remota (come, ad esempio, $ .get o $ ajax). Quindi recupero i callback impostati su di esso e li provo in modo discreto.

Ecco un esempio che ho visto di recente:

https://gist.github.com/946704


0

Prova jqueryspy.com Fornisce un'elegante sintassi simile a jquery per descrivere i tuoi test e consente ai callback di testare dopo che l'ajax è stato completato. È ottimo per i test di integrazione e puoi configurare i tempi di attesa massimi di Ajax in secondi o millesecondi.


0

Mi sento come se avessi bisogno di fornire una risposta più aggiornata poiché Jasmine è ora alla versione 2.4 e alcune funzioni sono cambiate dalla versione 2.0.

Quindi, per verificare che una funzione di callback sia stata chiamata all'interno della tua richiesta AJAX, devi creare una spia, aggiungere una funzione callFake ad essa, quindi utilizzare la spia come funzione di callback. Ecco come funziona:

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
        });
    });
});

Ho fatto il trucco per la funzione di successo e per la funzione di errore per assicurarmi che Jasmine eseguirà le specifiche il prima possibile anche se il tuo AJAX restituisce un errore.

Se non specifichi una funzione di errore e il tuo AJAX restituisce un errore, dovrai attendere 5 secondi (intervallo di timeout predefinito) finché Jasmine non genera un errore Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.. Puoi anche specificare il tuo timeout in questo modo:

it("should get the content of an XML file", function(done)
{
    // your code
},
10000); // 10 seconds
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.