Как проверить события jQuery AJAX с помощью Jasmine?

114

Я пытаюсь использовать Jasmine для написания некоторых спецификаций BDD для базовых запросов jQuery AJAX. В настоящее время я использую Jasmine в автономном режиме (т.е. через SpecRunner.html). Я настроил SpecRunner для загрузки jquery и других файлов .js. Есть идеи, почему следующее не работает? has_returned не сбылось, даже подумал "яппи!" предупреждение отображается нормально.

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

});

Как проверить, что обратный вызов был вызван? Мы будем очень благодарны за любые указатели на блоги / материалы, связанные с тестированием async jQuery с помощью Jasmine.

mnacos
источник

Ответы:

234

Думаю, вы можете провести два типа тестов:

  1. Модульные тесты, которые подделывают запрос AJAX (с использованием шпионов Jasmine), позволяя вам протестировать весь ваш код, который выполняется непосредственно перед запросом AJAX и сразу после него . Вы даже можете использовать Jasmine для подделки ответа с сервера. Эти тесты были бы быстрее - и им не нужно было бы обрабатывать асинхронное поведение, - поскольку настоящего AJAX не происходит.
  2. Интеграционные тесты, выполняющие реальные запросы AJAX. Они должны быть асинхронными.

Жасмин поможет вам пройти оба вида тестов.

Вот пример того, как можно подделать запрос AJAX, а затем написать модульный тест, чтобы убедиться, что поддельный запрос AJAX направлен на правильный 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"
    });
}

Для Jasmine 2.0 используйте вместо этого:

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

как отмечено в этом ответе

Вот аналогичный модульный тест, который проверяет, что ваш обратный вызов был выполнен после успешного выполнения запроса 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
    });
}

Для Jasmine 2.0 используйте вместо этого:

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

как отмечено в этом ответе

Наконец, в другом месте вы намекнули, что, возможно, захотите написать интеграционные тесты, которые делают реальные запросы AJAX - для целей интеграции. Это можно сделать с помощью асинхронных функций Jasmine: waits (), waitsFor () и run ():

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 Алекс отличный ответ. На самом деле я столкнулся с аналогичной проблемой, из-за которой я открыл вопрос. Проверить запрос Ajax с помощью объекта Deferred . Не могли бы вы взглянуть? Спасибо.
Лоррейн Бернар
12
Я действительно хочу, чтобы ваш ответ был частью официальной документации для Jasmine. Очень четкий ответ на проблему, которая убивала меня несколько дней.
Даррен Ньютон,
4
Этот ответ действительно следует отметить как официальный ответ на этот вопрос.
Thomas Amar
1
Текущий способ получить самую свежую информацию о звонках - $ .ajax.calls.mostRecent ()
camiblanch
1
Как бы вы реализовали это для простого JS ajax?
Часовщик
13

Посмотрите на проект jasmine-ajax: http://github.com/pivotal/jasmine-ajax .

Это вспомогательный помощник, который (для jQuery или Prototype.js) заглушает на уровне XHR, чтобы запросы никогда не уходили. Затем вы можете ожидать от запроса всего, что хотите.

Затем он позволяет вам предоставлять фиксированные ответы для всех ваших случаев, а затем писать тесты для каждого ответа, который вы хотите: успех, неудача, несанкционированный доступ и т. Д.

Он выводит вызовы Ajax из области асинхронных тестов и предоставляет вам большую гибкость для тестирования того, как должны работать ваши фактические обработчики ответов.

user533109
источник
Спасибо @jasminebdd, проект jasmine-ajax выглядит как способ тестирования моего js-кода. Но что, если бы я хотел протестировать фактические запросы к серверу, например, для тестов подключения / интеграции?
mnacos
2
@mnacos jasmine-ajax в основном полезен для модульного тестирования, и в этом случае вы специально хотите избежать обращения к серверу. Если вы проводите интеграционные тесты, это, вероятно, не то, что вам нужно, и его следует разработать как отдельную стратегию тестирования.
Себастьян Мартин
7

вот простой пример набора тестов для такого приложения js

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

образец набора тестов, который будет вызывать обратный вызов на основе регулярного выражения 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
источник
Спасибо приятель. Этот пример мне очень помог! Просто обратите внимание, что если вы используете Jasmine> 2.0, синтаксис для andCallFake будет spyOn (jQuery, «ajax»). And.callFake (/ * ваша функция * /);
Жуан Паулу Мотта,
Не уверен , что если это вопрос версии , но я получил ошибку на .andCallFake, используется .and.callFakeвместо этого. Спасибо.
OO
5

Когда я указываю код ajax с помощью Jasmine, я решаю проблему, отслеживая любую зависимую функцию, инициирующую удаленный вызов (например, $ .get или $ ajax). Затем я извлекаю установленные для него обратные вызовы и тестирую их дискретно.

Вот пример, который я недавно привел:

https://gist.github.com/946704

Джастин Сирлс
источник
0

Попробуйте jqueryspy.com. Он предоставляет элегантный синтаксис типа jquery для описания ваших тестов и позволяет проверять обратные вызовы после завершения ajax. Он отлично подходит для интеграционного тестирования, и вы можете настроить максимальное время ожидания ajax в секундах или миллисекундах.

Стив Томс
источник
0

Я чувствую, что мне нужно предоставить более свежий ответ, поскольку Jasmine теперь находится в версии 2.4, а некоторые функции были изменены по сравнению с версией 2.0.

Итак, чтобы убедиться, что функция обратного вызова была вызвана в вашем запросе AJAX, вам необходимо создать шпиона, добавить к нему функцию callFake, а затем использовать шпиона в качестве функции обратного вызова. Вот как это происходит:

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

Я проделал трюк для функции успеха, а также для функции ошибки, чтобы убедиться, что Jasmine выполнит спецификации как можно скорее, даже если ваш AJAX возвращает ошибку.

Если вы не укажете функцию ошибки и ваш AJAX вернет ошибку, вам придется подождать 5 секунд (интервал тайм-аута по умолчанию), пока Жасмин не выдаст ошибку Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.. Вы также можете указать свой собственный тайм-аут следующим образом:

it("should get the content of an XML file", function(done)
{
    // your code
},
10000); // 10 seconds
pmrotule
источник