如何伪造 jquery.ajax() 的响应?

40

我正在编写一些 QUnit 测试,测试 JavaScript 代码中的 AJAX 调用。

为了隔离测试,我覆盖了 $.ajax 方法,并将 AJAX 调用的参数数组写入了一个变量。这样可以测试方法如何使用 AJAX 函数,但是我在测试 $.load() 方法的成功处理程序时遇到了困难。

根据 http://api.jquery.com/load/ 文档:

当检测到成功响应时(即 textStatus 为 "success" 或 "notmodified"),.load() 将匹配元素的 HTML 内容设置为返回的数据。

因此,我尝试返回一个包含与成功处理程序中变量同名的对象的对象:

    //Mock ajax function
    $.ajax = function (param) {
        _mockAjaxOptions = param;
        var fakeAjaxSuccess = { responseText: "success", textStatus: "success", XMLHttpRequest: "success" };
        return fakeAjaxSuccess;
    };

但是这种方法没有起作用。

如何复制成功的 AJAX 调用行为?

7个回答

29

这个问题已经有几年了,对于新版本的jQuery有所改变。

使用Jasmin,您可以尝试Michael Falaga的方法

解决方案

  function ajax_response(response) {
    var deferred = $.Deferred().resolve(response);
    return deferred.promise;
  }

使用Jasmine

  describe("Test test", function() {
    beforeEach(function() {
      spyOn($, 'ajax').and.returnValue(
        ajax_response([1, 2, 3])
      );
    });
    it("is it [1, 2, 3]", function() {
      var response;
      $.ajax('GET', 'some/url/i/fancy').done(function(data) {
        response = data;
      });
      expect(response).toEqual([1, 2, 3]);
    });
  });

没有茉莉花

  $.ajax = ajax_response([1, 2, 3]);
  $.ajax('GET', 'some/url/i/fancy').done(function(data) {
     console.log(data); // [1, 2, 3]
  });
 

+one 非常感谢。 - whitesiroi
我得到了“ajax不是函数”的错误,它的完整信息是:{state: ƒ, always: ƒ, catch: ƒ, pipe: ƒ...} - Toskan
1
@Toskan 我也遇到了同样的问题。我猜想这个答案在2015年是正确的,但是在我今天写作时,“return deferred.promise();”这一行返回一个对象。你需要将其更改为“return deferred.promise;”,这样它就会返回 promise 函数。 - David Alan Condit
一直收到“deferred is not a function”错误。 - Jiří

15

在受到@Robusto和@Val的启发后,我找到了一个可行的方法:

//Mock ajax function
$.ajax = function (param) {
    _mockAjaxOptions = param;
    //call success handler
    param.complete("data", "textStatus", "jqXHR");
};

我并没有从任何真实的 $.ajax 代码中引发事件,也没有触发任何事件,而是让我的假 ajax 对象在其内部函数中调用传递给 $.ajax() 的函数(作为一个参数)。


2
虽然可能有点晚了,但你也许一直在寻找 https://github.com/appendto/jquery-mockjax。 - xhh
@xhh 这是一个非常好的框架来设置返回值。 - StuperUser
@PeterSmith 你为什么要添加另一个函数?由于JS是动态类型的,你可以直接在原地覆盖它。除了封装之外,它给你带来了什么好处吗? - StuperUser
2
那么,你究竟要怎样处理这个未声明的 _mockAjaxOptions 变量呢?它似乎就悬在空中。 - vsync
如果我没记错的话,我在测试的断言步骤中声明了 _mockAjaxOptions 并在外部进行了检查。 - StuperUser
显示剩余4条评论

12

使用闭包覆盖 $.ajax 以返回虚假响应

在尝试了被接受的答案由用户1634074发布的答案后,我设计出这种简单而灵活的混合方式。

最基本的形式如下所示...

function ajax_response(response) {
  return function (params) {
    params.success(response);
  };
}
$.ajax = ajax_response('{ "title": "My dummy JSON" }');

在上面的例子中,定义一个函数`ajax_response()`作为参数接受一些JSON字符串(或任何有用于模拟响应的自定义参数)并返回一个匿名闭包函数,该函数将被分配给$.ajax以进行单元测试的重写。
匿名函数接受一个`params`参数,该参数将包含传递给`$.ajax`函数的设置对象。它使用传递给外部函数的参数来模拟来自服务器的响应。在这个例子中,它总是通过简单调用`success`回调函数并提供虚拟JSON来模拟来自服务器的成功响应。 很容易重新配置...
function ajax_response(response, success) {
  return function (params) {
    if (success) {
      params.success(response);
    } else {
      params.error(response);
    }
  };
}

// Simulate success
$.ajax = ajax_response('{ "title": "My dummy JSON." }', true); 
doAsyncThing(); // Function that calls $.ajax

// Simulate error
$.ajax = ajax_response('{ "error": "Who is the dummy now?" }', false); 
doAsyncThing(); // Function that calls $.ajax

以下我们可以看到它的实际运用...

/* FUNCTION THAT MAKES AJAX REQUEST */
function doAsyncThing() {
  $.ajax({
    type: "POST",
    url: "somefile.php",
    // data: {…},
    success: function (results) {
      var json = $.parseJSON(results),
          html = $('#ids').html();
      $('#ids').html(html + '<br />' + json.id);
    }
  });
}

/* BEGIN MOCK TEST */
// CREATE CLOSURE TO RETURN DUMMY FUNCTION AND FAKE RESPONSE
function ajax_response(response) {
  return function (params) {
    params.success(response);
  };
}

var n = prompt("Number of AJAX calls to make", 10);

for (var i = 1; i <= n; ++i) {
  
  // OVERRIDE $.ajax WITH DUMMY FUNCTION AND FAKE RESPONSE
  $.ajax = ajax_response('{ "id": ' + i + ' }');
  doAsyncThing();
}
/* END MOCK TEST */
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="ids">IDs:</p>


6

在不干扰jQuery的情况下根据需要模拟$.ajax请求

这里的答案很好,但我有一个具体的需求:在后端服务构建完成之前,需要对单个API调用构建一个虚假响应,而将所有其他API调用保持不变,以便我可以继续在UI上构建内容。

API对象在底层使用了$.ajax,因此您可以这样调用API方法:

api.products({ price: { $lt: 150, tags: ['nike', 'shoes'] } })
.done(function(json) {
  // do something with the data
})
.error(function(err) {
  // handle error
});

这个方法可以解决问题:
function mockAjax(options) {
  var that = {
    done: function done(callback) {
      if (options.success)
        setTimeout(callback, options.timeout, options.response);
      return that;
    },
    error: function error(callback) {
      if (!options.success)
        setTimeout(callback, options.timeout, options.response);
      return that;
    }
  };
  return that;
}

不用改动 $.ajax,就可以覆盖单个 API ��用:

api.products = function() {
  return mockAjax({
    success: true,
    timeout: 500,
    response: {
      results: [
        { upc: '123123', name: 'Jordans' },
        { upc: '4345345', name: 'Wind Walkers' }
      ]
    }
  });
};

https://jsfiddle.net/Lsf3ezaz/2/


1

我觉得下面的链接应该会有帮助。至于参数,我不太确定,但可能是。

$.fn.ajax.success =  function (){
  ///the rest goest here
}

如何覆盖jQuery的.val()函数?


这句话没有任何意义...浏览器仍然会发送请求。它似乎只是覆盖了成功函数。 - vsync

1
这是一个简单的可行方案。
var set_ajax_response = function(data){
  $.ajax = $.Deferred().resolve(data).promise;
}

var data = [1,2,3];

set_ajax_response(data);

不断收到“deferred is not a function”错误。 - Jiří

1
看一下jQuery文档:你会发现Ajax设置提供了很多其他测试条件。如果你让它们都指向你的fakeAjaxSuccess,你可能会实现你的目标。
或者,将你的$.ajax调用封装到它自己的函数中,让任何调用它的东西只需使用fakeAjaxSuccess对象来调用你的事件处理程序即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接