【问题标题】:Mocking nested function returning Deferred in Jasmine在 Jasmine 中模拟返回 Deferred 的嵌套函数
【发布时间】:2015-03-25 21:10:30
【问题描述】:

我正在尝试为使用名为 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() 函数,以便它返回我在测试中定义的 Json 有效负载。 read() 方法返回一个 $.Deferred 对象。

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

【问题讨论】:

    标签: javascript jquery unit-testing jasmine


    【解决方案1】:

    我看到了两种方法:

    1. spy $.ajax() 并调用返回您自己的延迟的假函数

      相反:你间接测试了库

    2. 模拟$.RestClients 接口并返回您自己的延迟

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


    TL;DR如果已知,请跳过此内容。

    但首先让我们看看 RestClient 是如何工作的……它有两个基本对象,一个 Resource 和一个 Verb。 RestClient 实际上是一个Resource 对象(1)。 Resource 对象将返回另一个 Resource 对象,而 add()ing 一个休息片段 (2)。预定义动词read 将返回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() 的调用,并返回一个承诺。

    所以,当你使用新的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();
        });
    });
    

    fake 正在返回一个 deferred,而不是一个 promise,但在这种情况下它是可以的,因为它具有相同的接口,除了我们之外,没有人/没有人应该拒绝或解决 deferred。

    变体 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 的模拟需要比我做的更多的工作,使用上面的代码这是不可能的。

    【讨论】:

    • 糟糕的答案!谢谢
    猜你喜欢
    • 1970-01-01
    • 2021-06-18
    • 1970-01-01
    • 2011-07-18
    • 1970-01-01
    • 2019-03-12
    • 1970-01-01
    • 2020-03-21
    • 1970-01-01
    相关资源
    最近更新 更多