Wie überprüfe ich jQuery AJAX-Ereignisse mit Jasmine?

114

Ich versuche, Jasmine zu verwenden, um einige BDD-Spezifikationen für grundlegende jQuery AJAX-Anforderungen zu schreiben. Ich verwende Jasmine derzeit im Standalone-Modus (dh bis SpecRunner.html). Ich habe SpecRunner so konfiguriert, dass jquery und andere .js-Dateien geladen werden. Irgendwelche Ideen, warum das Folgende nicht funktioniert? has_returned wird nicht wahr, obwohl das "yuppi!" Alarm wird gut angezeigt.

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

});

Wie teste ich, ob der Rückruf aufgerufen wurde? Alle Hinweise auf Blogs / Materialien im Zusammenhang mit dem Testen von asynchronem jQuery mit Jasmine werden sehr geschätzt.

Mnacos
quelle

Antworten:

234

Ich denke, es gibt zwei Arten von Tests, die Sie durchführen können:

  1. Unit-Tests, die die AJAX-Anfrage fälschen (unter Verwendung von Jasmines Spionen), ermöglichen es Ihnen, Ihren gesamten Code zu testen, der unmittelbar vor der AJAX-Anfrage und kurz danach ausgeführt wird . Sie können sogar Jasmine verwenden, um eine Antwort vom Server zu fälschen. Diese Tests wären schneller - und sie müssten nicht mit asynchronem Verhalten umgehen -, da keine echte AJAX stattfindet.
  2. Integrationstests, die echte AJAX-Anforderungen ausführen. Diese müssten asynchron sein.

Jasmine kann Ihnen bei beiden Arten von Tests helfen.

Hier ist ein Beispiel, wie Sie die AJAX-Anforderung fälschen und anschließend einen Komponententest schreiben können, um zu überprüfen, ob die gefälschte AJAX-Anforderung an die richtige URL gesendet wurde:

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

Verwenden Sie für Jasmine 2.0 stattdessen:

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

wie in dieser Antwort angegeben

Hier ist ein ähnlicher Komponententest, der bestätigt, dass Ihr Rückruf ausgeführt wurde, nachdem eine AJAX-Anforderung erfolgreich abgeschlossen wurde:

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

Verwenden Sie für Jasmine 2.0 stattdessen:

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

wie in dieser Antwort angegeben

Schließlich haben Sie an anderer Stelle angedeutet, dass Sie möglicherweise Integrationstests schreiben möchten, die echte AJAX-Anforderungen stellen - für Integrationszwecke. Dies kann mithilfe der asynchronen Funktionen von Jasmine erfolgen: waits (), waitsFor () und running ():

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
    });
}
Alex York
quelle
+1 Alex tolle Antwort. Tatsächlich hatte ich ein ähnliches Problem, für das ich eine Frage zum Testen von Ajax mit dem Objekt "Zurückgestellt" geöffnet habe . Könnten Sie einen Blick darauf werfen? Vielen Dank.
Lorraine Bernard
12
Ich wünschte wirklich, Ihre Antwort wäre Teil der offiziellen Dokumentation für Jasmine. Sehr klare Antwort auf ein Problem, das mich seit einigen Tagen umbringt.
Darren Newton
4
Diese Antwort sollte wirklich als offizielle Antwort auf diese Frage markiert werden.
Thomas Amar
1
Der aktuelle Weg, um die neuesten Anrufinformationen zu erhalten, ist $ .ajax.calls.mostRecent ()
camiblanch
1
Wie würden Sie das für einfachen JS Ajax implementieren?
Uhrmacher
13

Schauen Sie sich das Jasmin-Ajax-Projekt an: http://github.com/pivotal/jasmine-ajax .

Es ist ein Drop-In-Helfer, der (entweder für jQuery oder Prototype.js) auf der XHR-Ebene stoppt, sodass Anforderungen nie ausgehen. Sie können dann alles erwarten, was Sie über die Anfrage wollen.

Anschließend können Sie Fixture-Antworten für alle Ihre Fälle bereitstellen und dann Tests für jede gewünschte Antwort schreiben: Erfolg, Misserfolg, nicht autorisiert usw.

Es nimmt Ajax-Aufrufe aus dem Bereich der asynchronen Tests heraus und bietet Ihnen viel Flexibilität beim Testen der Funktionsweise Ihrer tatsächlichen Antworthandler.

user533109
quelle
Vielen Dank an @jasminebdd, das Jasmin-Ajax-Projekt scheint der richtige Weg zu sein, um meinen js-Code zu testen. Was aber, wenn ich mit tatsächlichen Anforderungen an den Server testen wollte, z. B. für Konnektivitäts- / Integrationstests?
Mnacos
2
@mnacos jasmine-ajax ist hauptsächlich für Unit-Tests nützlich. In diesem Fall möchten Sie den Anruf beim Server ausdrücklich vermeiden. Wenn Sie Integrationstests durchführen, ist dies wahrscheinlich nicht das, was Sie möchten, und sollte als separate Teststrategie konzipiert werden.
Sebastien Martin
7

Hier ist ein einfaches Beispiel für eine Testsuite für eine App wie diese

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

Eine Beispiel-Testsuite, die einen Rückruf basierend auf der URL regexp aufruft

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();
 });
});
übersprungen
quelle
Danke Alter. Dieses Beispiel hat mir sehr geholfen! Beachten Sie nur, dass bei Verwendung von Jasmine> 2.0 die Syntax für andCallFake spyOn (jQuery, "ajax") lautet. And.callFake (/ * Ihre Funktion * /);
João Paulo Motta
Ich bin mir nicht sicher, ob dies ein Versionsproblem ist, aber ich habe einen Fehler erhalten .andCallFake, der .and.callFakestattdessen verwendet wird. Vielen Dank.
OO
5

Wenn ich mit Jasmine Ajax-Code spezifiziere, löse ich das Problem, indem ich die abhängige Funktion ausspioniere, die den Remote-Aufruf initiiert (z. B. $ .get oder $ ajax). Dann rufe ich die darauf eingestellten Rückrufe ab und teste sie diskret.

Hier ist ein Beispiel, das ich kürzlich aufgezeigt habe:

https://gist.github.com/946704

Justin Searls
quelle
0

Versuchen Sie es mit jqueryspy.com. Es bietet eine elegante jquery-ähnliche Syntax zur Beschreibung Ihrer Tests und ermöglicht Rückrufe zum Testen, nachdem der Ajax abgeschlossen ist. Es eignet sich hervorragend für Integrationstests und Sie können maximale Ajax-Wartezeiten in Sekunden oder Millesekunden konfigurieren.

Steve Toms
quelle
0

Ich habe das Gefühl, dass ich eine aktuellere Antwort geben muss, da Jasmine jetzt auf Version 2.4 ist und einige Funktionen gegenüber Version 2.0 geändert wurden.

Um zu überprüfen, ob eine Rückruffunktion in Ihrer AJAX-Anfrage aufgerufen wurde, müssen Sie einen Spion erstellen, eine callFake-Funktion hinzufügen und dann den Spion als Rückruffunktion verwenden. So geht's:

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

Ich habe den Trick sowohl für die Erfolgsfunktion als auch für die Fehlerfunktion ausgeführt, um sicherzustellen, dass Jasmine die Spezifikationen so schnell wie möglich ausführt, auch wenn Ihr AJAX einen Fehler zurückgibt.

Wenn Sie keine Fehlerfunktion angeben und Ihr AJAX einen Fehler zurückgibt, müssen Sie 5 Sekunden (Standard-Timeout-Intervall) warten, bis Jasmine einen Fehler auslöst Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.. Sie können auch Ihr eigenes Timeout wie folgt angeben:

it("should get the content of an XML file", function(done)
{
    // your code
},
10000); // 10 seconds
pmrotule
quelle