Jasmine 测试以验证回调不会引发错误

Jasmine test to verify that a callback does not raise an error

如何编写 Jasmine 测试来验证异步回调确实不会引发错误?

我正在用这个基本模式编写一个函数:

myFunction: function (callback) {
   this.doSomethingAsync(function () {
      if (callback) {
          callback();
      }
    });
    return "done";
 }

参数callback是可选的。因此,如果用户未提供回调,if 语句将跳过回调。

我想编写一个 Jasmine 测试,如果将来有人删除 if 语句,该测试将失败。但是,我想不出如何构建测试。

删除 if 语句并调用 myFunction 的结果是 Javascript 引发此 TypeError

Uncaught TypeError: callback is not a function

但是,我想不出如何设置 Jasmine 测试来验证没有出现此类错误。

你可以去掉 doSomethingAsync 函数,让它只调用回调函数

describe('doSomthingAsync', function(){
  function A(){
    this.doSomethingAsync = function(cb){
      setTimeout(function(){
        cb();
      },2000);
    }
    this.myFunction = function(cb){
      this.doSomethingAsync(function(){
          cb();
      })
    }
  }

  it('tests for the throws', function(){
    var a = new A();
    a.doSomethingAsync = function(cb){
      cb();
    }

    var func = function(){
      a.myFunction();
    };

    var func2 = function(){
      a.myFunction(function(){
        console.log('ping');
      });
    }

    expect(func).toThrow(Error);
    expect(func2).not.toThrow(Error);
  });
});

尽管如此,我可能会通过放置代码来检查回调是否在其他地方定义来强制执行回调是可选的这一事实。

...
this.doSomethingAsync = function(cb){
    setTimeout(function(){
        if(cb && typeof cb === 'function'){
            cb();
        }
        //do other stuff
    }, 2000)
}

首先,您可以通过模拟相关函数来序列化异步部分以同步执行。然后,您可以使用 .not.toThrow() 期望来测试预期行为。

假设这个演示对象:

var objUnderTest = {

    doSomethingAsync: function(fn) {
        console.info("Doing something async.");

        setTimeout(function(){
            console.info("Done something async.");
            fn();
        }, 1000);
    },

    myFunction: function(callback) {
        this.doSomethingAsync(function() {
            if (callback) {
                callback();
            }
        });

        return "done";
    }
};

您的测试可能如下所示:

describe("Test", function(){
    beforeEach(function(){
        // serialize calls for easier testing
        // now your closure passed to doSomethingAsync() will directly be called
        spyOn(objUnderTest, "doSomethingAsync").and.callFake(function(fn){
            fn();
        });
    });

    it("should call the callback on myFunction(<callack>)", function() {
        var callback = jasmine.createSpy("callback");
        objUnderTest.myFunction(callback);
        expect(callback).toHaveBeenCalled();
    });

    it("should not throw on myFunction() without callback", function(){
        expect(function(){
            objUnderTest.myFunction();
        }).not.toThrow();
    });
});

因为您只想测试您的 myFunction 行为是否正确,我认为模拟 doSomethingAsync 没问题,即使它是被测模块的一部分。

如果您不想模拟被测对象中的某个方法,您可以模拟掉 doSomethingAsync() 方法调用的所有方法。

假设您需要模拟演示对象 setTimeout():

spyOn(window, "setTimeout").and.callFake(function(fn){
    fn();
});

我在进行 karma-jasmine 测试时遇到了这个问题,以检查如果正确的 init 在超时之前没有发生,我的库是否会抛出异常。虽然可以选择提供您自己的 setTimeout,但我希望让测试尽可能接近生产条件。我解决了这个

//...some code that sets up eventual failure
var oldOnError = window.onerror;
var thrownMessage: string;
window.onerror = function(msg){
    thrownMessage = msg;
}

window.setTimeout(function(){
  window.onerror = oldOnError;
  expect(thrownMessage).toBeDefined();
  expect(thrownMessage.indexOf("unresolved") > -1).toBe(true);
  done();
}, 100);

在 node 中你可能会使用这个(虽然还没有测试过)

process.on('uncaughtException', function (err) {
  expect(....);
  done();
})