Модульное тестирование запросов AJAX с помощью QUnit - PullRequest
6 голосов
/ 24 февраля 2012

Мы пытаемся реализовать JavaScript-тесты QUnit для JS-насыщенного веб-приложения. Мы изо всех сил пытаемся найти способ успешно протестировать методы, которые включают запросы jQuery AJAX. Например, у нас есть следующая функция конструктора (очевидно, это очень упрощенный пример):

var X = function() {
    this.fire = function() {
        $.ajax("someURL.php", {
            data: {
                userId: "james"
            },
            dataType: "json",
            success: function(data) {
                //Do stuff
            }
        });
    };
};
var myX = new X();
myX.fire();

Мы пытаемся найти способ протестировать метод fire, желательно с заглушенным URL-адресом вместо реального someURL.php.

Единственное очевидное решение для меня на данный момент - добавить URL и обратный вызов success в качестве аргументов функции конструктора. Таким образом, в тесте мы можем создать новый экземпляр X и передать URL-адрес заглушки и обратный вызов для запуска, когда заглушка возвращает ответ. Например:

test("Test AJAX function", function() {
    stop();
    var myX = new X();
    //Call the AJAX function, passing in the stub URL and success callback
    myX.fire("stub.php", function(data) {
        console.log(data);
        start();
    });
});

Однако это не очень хорошее решение. Есть ли лучший способ?

Ответы [ 2 ]

9 голосов
/ 24 февраля 2012

С помощью jQuery вы можете использовать объект xhr, который .ajax() возвращает как обещание, так что вы можете добавить больше обработчиков (см. Ниже), чем только одиночные success, complete и error, которые вы определили вварианты.Поэтому, если ваша асинхронная функция может возвращать объект xhr, вы можете добавить специфичные для теста обработчики.

Что касается URL, то это немного сложнее.Иногда я настраивал очень простой Node-сервер на localhost, который просто обслуживает стандартные ответы, которые были скопированы с реального сервера.Если вы запускаете свой тестовый набор с того же сервера, ваши URL-адреса просто должны быть абсолютными путями для попадания на тестовый сервер, а не на рабочий сервер.И вы также получаете запись самих запросов, так как сервер их видит.Или вы можете сделать так, чтобы тестовый сервер преднамеренно отправлял сообщения об ошибках или неправильных ответах, если вы хотите увидеть, как код обрабатывает его.

Но это, конечно, довольно сложное решение.Проще было бы определить ваши URL в месте, где вы можете переопределить их из набора тестов.Например:

/* in your code */
var X = function () {
    this.fire = function () {
        return $.ajax({ url: this.constructor.url, ... });
    };
};
X.url = "someURL.php"; // the production url

/* in your tests */
X.url = "stub.php"; // redefine to the test url

Кроме того, в QUnit есть функция asyncTest, которая вызывает для вас stop().Добавьте крошечный помощник, чтобы отслеживать, когда начинать снова, и у вас есть довольно хорошее решение.

Вот что я сделал раньше

// create a function that counts down to `start()`
function createAsyncCounter(count) {
    count = count || 1; // count defaults to 1
    return function () { --count || start(); };
}

// ....

// an async test that expects 2 assertions
asyncTest("testing something asynchronous", 2, function() {
    var countDown = createAsyncCounter(1), // the number of async calls in this test
        x = new X;

    // A `done` callback is the same as adding a `success` handler
    // in the ajax options. It's called after the "real" success handler.
    // I'm assuming here, that `fire()` returns the xhr object
    x.fire().done(function(data, status, jqXHR) {
        ok(data.ok);
        equal(data.value, "foobar");
    }).always(countDown); // call `countDown` regardless of success/error
});

В основном countDown - этофункция, которая отсчитывает от нуля все, что вы укажете, а затем вызывает start().В этом случае есть 1 асинхронный вызов, поэтому countDown будет отсчитывать от этого.И это будет сделано, когда вызов ajax завершится, независимо от того, как он прошел, поскольку он настроен как обратный вызов always.
И поскольку asyncTest говорят, что он ожидает 2 утверждения, он сообщитошибка, если обратный вызов .done() никогда не вызывается, так как никакие утверждения не будут выполнены.Так что, если звонок полностью провалится, вы тоже об этом узнаете.Если вы хотите что-то записать при ошибке, вы можете добавить обратный вызов .fail() в цепочку обещаний.

3 голосов
/ 11 мая 2014

Если это модульный тест, который можно (и нужно) запускать изолированно от серверной стороны, вы можете просто «заменить» $.ajax для имитации любого поведения.Один простой пример:

test("Test AJAX function", function() {
  // keep the real $.ajax
  var _real_ajax = $.ajax;

  // Simulate a successful response
  $.ajax = function(url, opts) {
    opts.success({expected: 'response'});
  }

  var myX = new X();
  // Call your ajax function
  myX.fire();
  // ... and perform your tests

  // Don't forgot to restore $.ajax!
  $.ajax = _real_ajax;
});

Очевидно, что вы также можете выполнить реальный вызов ajax с URL-адресом / данными с заглушкой:

// Simulate a successfully response
$.ajax = function(url, opts) {
  opts.success = function(data) { 
    console.log(data);
    start();
  }
  _real_ajax('stub.php', opts)
}

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

...