Jak zweryfikować zdarzenia jQuery AJAX za pomocą Jasmine?

114

Próbuję użyć Jasmine do napisania niektórych specyfikacji BDD dla podstawowych żądań jQuery AJAX. Obecnie używam Jasmine w trybie samodzielnym (tj. Do końca SpecRunner.html). Skonfigurowałem SpecRunner do ładowania jquery i innych plików .js. Jakieś pomysły, dlaczego poniższe nie działają? has_returned nie staje się prawdą, nawet pomyślał, że "yuppi!" alert pojawia się dobrze.

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

});

Jak sprawdzić, czy połączenie zwrotne zostało wywołane? Wszelkie wskazówki do blogów / materiałów związanych z testowaniem async jQuery z Jasmine będą bardzo mile widziane.

mnacos
źródło

Odpowiedzi:

234

Sądzę, że są dwa rodzaje testów, które możesz wykonać:

  1. Testy jednostkowe, które fałszują żądanie AJAX (przy użyciu szpiegów Jasmine), umożliwiając przetestowanie całego kodu, który działa tuż przed żądaniem AJAX i tuż po nim . Możesz nawet użyć Jasmine do sfałszowania odpowiedzi z serwera. Te testy byłyby szybsze - i nie musiałyby obsługiwać asynchronicznego zachowania - ponieważ nie ma prawdziwego AJAX.
  2. Testy integracyjne wykonujące rzeczywiste żądania AJAX. Musiałyby być asynchroniczne.

Jasmine może pomóc ci wykonać oba rodzaje testów.

Oto przykład, w jaki sposób można sfałszować żądanie AJAX, a następnie napisać test jednostkowy, aby sprawdzić, czy sfałszowane żądanie AJAX trafiało pod właściwy adres URL:

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

W przypadku Jasmine 2.0 użyj zamiast tego:

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

jak zaznaczono w tej odpowiedzi

Oto podobny test jednostkowy, który weryfikuje wykonanie wywołania zwrotnego po pomyślnym zakończeniu żądania AJAX:

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

W przypadku Jasmine 2.0 użyj zamiast tego:

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

jak zaznaczono w tej odpowiedzi

Na koniec zasugerowałeś gdzie indziej, że możesz chcieć napisać testy integracji, które będą wykonywać prawdziwe żądania AJAX - do celów integracji. Można to zrobić za pomocą asynchronicznych funkcji Jasmine: Waits (), WaitsFor () i działa ():

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
źródło
+1 Alex świetna odpowiedź. Właściwie napotkałem podobny problem, dla którego otworzyłem pytanie Test Ajax request za pomocą obiektu Deferred . Czy mógłbyś spojrzeć? dzięki.
Lorraine Bernard
12
Naprawdę chciałbym, żeby twoja odpowiedź była częścią oficjalnej dokumentacji Jasmine. Bardzo jasna odpowiedź na problem, który zabija mnie od kilku dni.
Darren Newton,
4
Ta odpowiedź naprawdę powinna być oznaczona jako oficjalna odpowiedź na to pytanie.
Thomas Amar
1
Bieżący sposób uzyskiwania najnowszych informacji o połączeniach to $ .ajax.calls.mostRecent ()
camiblanch
1
Jak zaimplementowałbyś to dla zwykłego AJAX JS?
Zegarmistrz
13

Spójrz na projekt jasmine-ajax: http://github.com/pivotal/jasmine-ajax .

Jest to pomocnik typu drop-in, który (zarówno w przypadku jQuery, jak i Prototype.js) zapisuje kod w warstwie XHR, dzięki czemu żądania nigdy nie wychodzą. Możesz wtedy oczekiwać wszystkiego, co chcesz w tej prośbie.

Następnie umożliwia dostarczanie odpowiedzi na urządzenia dla wszystkich przypadków, a następnie pisanie testów dla każdej żądanej odpowiedzi: sukcesu, niepowodzenia, braku autoryzacji itp.

Wyprowadza wywołania Ajax z królestwa testów asynchronicznych i zapewnia dużą elastyczność podczas testowania, jak powinny działać rzeczywiste programy obsługi odpowiedzi.

user533109
źródło
Dzięki @jasminebdd, projekt jasmine-ajax wygląda na sposób na przetestowanie mojego kodu js. Ale co, jeśli chciałbym przetestować rzeczywiste żądania do serwera, np. Testy łączności / integracji?
mnacos
2
@mnacos jasmine-ajax jest szczególnie przydatne do testowania jednostkowego, w którym to przypadku chcesz szczególnie uniknąć wywołania serwera. Jeśli robisz testy integracyjne, prawdopodobnie nie tego chcesz i powinno być zaprojektowane jako osobna strategia testowa.
Sebastien Martin
7

tutaj jest prosty przykładowy zestaw testów dla aplikacji js takiej jak ta

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

przykładowy zestaw testów, który wywoła wywołanie zwrotne na podstawie wyrażenia regularnego 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();
 });
});
skipy
źródło
Dzięki stary. Ten przykład bardzo mi pomógł! Zwróć uwagę, że jeśli używasz Jasmine> 2.0, składnia andCallFake to spyOn (jQuery, „ajax”). And.callFake (/ * twoja funkcja * /);
João Paulo Motta
Nie jestem pewien, czy jest to problem z wersją, ale pojawił się błąd .andCallFake, używany .and.callFakezamiast tego. Dzięki.
OO
5

Kiedy określam kod Ajax w Jasmine, rozwiązuję problem, szpiegując jakąkolwiek zależną funkcję inicjującą zdalne wywołanie (np. $ .Get lub $ ajax). Następnie pobieram ustawione na nim wywołania zwrotne i dyskretnie je testuję.

Oto przykład, który niedawno podałem:

https://gist.github.com/946704

Justin Searls
źródło
0

Wypróbuj jqueryspy.com Zapewnia elegancką składnię podobną do jquery do opisywania testów i umożliwia testowanie wywołań zwrotnych po zakończeniu ajax. Jest świetny do testowania integracji i można skonfigurować maksymalny czas oczekiwania Ajax w sekundach lub milesekundach.

Steve Toms
źródło
0

Czuję, że muszę udzielić bardziej aktualnej odpowiedzi, ponieważ Jasmine jest teraz w wersji 2.4 i kilka funkcji zmieniło się w porównaniu z wersją 2.0.

Tak więc, aby sprawdzić, czy funkcja wywołania zwrotnego została wywołana w żądaniu AJAX, musisz utworzyć szpiega, dodać do niego funkcję callFake, a następnie użyć szpiega jako funkcji zwrotnej. Oto jak to działa:

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

Zrobiłem sztuczkę dla funkcji sukcesu, a także funkcji błędu, aby upewnić się, że Jasmine uruchomi specyfikację tak szybko, jak to możliwe, nawet jeśli twój AJAX zwróci błąd.

Jeśli nie określisz funkcji błędu, a AJAX zwróci błąd, będziesz musiał poczekać 5 sekund (domyślny limit czasu), aż Jasmine zgłosi błąd Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.. Możesz również określić swój własny limit czasu w następujący sposób:

it("should get the content of an XML file", function(done)
{
    // your code
},
10000); // 10 seconds
pmrotule
źródło