在 Jasmine 中模拟嵌套函数返回 Deferred

Mocking nested function returning Deferred in Jasmine

我正在尝试为使用名为 jquery.rest

的 jQuery ajax 包装器库的模块编写 Jasmine 测试

正在测试的模块:

var module = function() {

  function getData(callback) {

    IP.read().done(function (data) {
        console.log("done");
        callback(data);
    });
  }

  return {
    getData: getData
  }

}();

clientIP 变量在不同的文件中声明,如下所示:

var client = new $.RestClient('/rest/api/');
var IP = client.add('ip');

我想模拟 read() 函数,以便它 return 我在测试中定义的 Json 有效载荷。 read() 方法 return 是一个 $.Deferred 对象。

我尝试了不同的方法(使用 Jasmine 间谍)但没有成功。

我看到有两种方法可以做到这一点:

  1. 监视 $.ajax() 并调用 return 自己延迟的假函数

    相反:您间接测试了库

  2. mock $.RestClients 接口和return你自己的延迟

    相反:当不仅需要测试回调时,还需要更多的工作来模拟库。 (您的模拟越复杂,您的测试就越容易出错。)


TL;DR 如果知道请跳过。

但首先让我们看看 RestClient 是如何工作的...它有两个基本对象,一个 Resource 和一个 Verb。 RestClient 实际上是一个 Resource 对象 (1)。 Resource 个对象将 return 另一个 Resource 个对象,当 add() 一个剩余片段 (2) 时。预定义动词 read 将 return 一个 Verb 实例的 call 方法 (3).

  1. https://github.com/jpillora/jquery.rest/blob/gh-pages/dist/jquery.rest.js#L382
  2. https://github.com/jpillora/jquery.rest/blob/gh-pages/dist/jquery.rest.js#L241
  3. https://github.com/jpillora/jquery.rest/blob/gh-pages/dist/jquery.rest.js#L245

从该链的底部到顶部,可以从 call() 方法 (4) 访问 request 方法。如果未明确覆盖,则默认为 $.ajax()。 (5)

  1. https://github.com/jpillora/jquery.rest/blob/gh-pages/dist/jquery.rest.js#L174
  2. https://github.com/jpillora/jquery.rest/blob/gh-pages/dist/jquery.rest.js#L66

如果没有进行不同的配置,对 read() 的调用将导致对 $.ajax() 的调用,return 承诺。

因此,在执行新的 new $.RestClient().add("...").add("...").read() 时,您将获得与 $.ajax() 相同的结果。


变体 1:

describe("getData()", function(){
        // Handle to ajax()' deferred, scoped to the
        // describe so the fake ajax() and the it()
        // have access to it
    var def,
        underTest;

    beforeEach(function(){
        // Mock $.ajax (or what a Verb calls in the end)
        // assign "def" with a deferred and return it,
        // the test can then resolve() or reject() it
        spyOn($, "ajax").and.callFake(function(opts) {
            def = $.Deferred();
            return def;
        });

        // This is under test
        underTest = new UnderTest();
    });

    afterEach(function(){
        // Ensure a missing call of ajax() will fail the test
        def = null;
    });

    it("should call callback on successful read", function() {
        var callback = jasmine.createSpy("callback");
        // Indirectly call ajax() which will create def
        underTest.getData(callback);
        // Resolve the deferred to succeed the response
        def.resolve({a: 1});
        expect(callback).toHaveBeenCalledWith({a: 1});
    });

    it("should not call callback on failed read", function(){
        var callback = jasmine.createSpy("callback");
        underTest.getData(callback);
        def.reject();
        expect(callback).not.toHaveBeenCalled();
    });
});

伪造的是 return 延期,而不是承诺,但在这种情况下没关系,因为它具有相同的接口,并且 nothing/no 除了我们之外,应该拒绝或解决这里的延期.

变体 2:

describe("getData()", function(){
        // Store original reference
    var origRestClient,
        // See first code block
        def,
        underTest;

    // Mock thr Resouce object
    function MockResource() { }

    // Simplify the behaviour of this mock,
    // return another Mock resource
    MockResource.prototype.add = function() {
        return new MockResource();
    };

    // What Verb.call() would do, but directly
    // return a deferred
    MockResource.prototype.read = function() {
        def = $.Deferred();
        return def;
    };

    beforeEach(function(){
        // Replace RestClient
        origRestClient = $.RestClient;
        $.RestClient = MockResource;

        underTest = new UnderTest();
    });

    afterEach(function(){
        // Restore RestClient
        $.RestClient = origRestClient;
        def = null;
    });

    it("should call callback on successful read", function() {
        var callback = jasmine.createSpy("callback");
        underTest.getData(callback);
        def.resolve({a: 1});
        expect(callback).toHaveBeenCalledWith({a: 1});
    });

    it("should not call callback on failed read", function(){
        var callback = jasmine.createSpy("callback");
        underTest.getData(callback);
        def.reject();
        expect(callback).not.toHaveBeenCalled();
    });
});

如果您想测试路径和请求数据,Resouce 的模拟需要比我所做的更多的工作,使用上面的代码这是不可能的。