Jasmine test to verify that callback does not throw an error

How do I write a Jasmine test to make sure the asynchronous callback doesn't throw an error?

I am writing a function with this basic pattern:

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

      

This parameter callback

is optional. Thus, the operator if

skips the callback if the user does not send it.

I want to write a Jasmine test that fails if someone removes the statement if

in the future. However, I cannot think of how to structure the test.

The consequence of removing the operator if

and calling myFunction

is that Javascript calls this TypeError

:

Uncaught TypeError: callback is not a function

      

However, I cannot think of how to set up the Jasmine test to make sure this error does not occur.

+3


source to share


3 answers


First, you can serialize the asynchronous part by mocking related functions with execute synchronously . Then you can test the expected behavior using expectations .not.toThrow()

.

Assuming this demo object:

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";
    }
};

      

Your test might look like this:

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();
    });
});

      



Since you only want to test that yours myFunction

is behaving correctly, I think it's okay to mock doSomethingAsync

, even if it's part of a module under the test.

If you don't want to mock the method in the test object, you can mock all the methods called by the method doSomethingAsync()

.

Assuming the demo object you will need to mock setTimeout()

:

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

      

+1


source


You can stub the doSomethingAsync function and only call its callback function

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);
  });
});

      



Though, I would probably respect the fact that the callback is optional by putting in the code to check if the callback is defined elsewhere.

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

      

+2


source


I had this problem when we were doing karma jasmine tests to verify that my library throws an exception if correct initialization fails before timeout. While it is possible to provide your own setTimeout

, I wanted to keep the test as close to the terms of the product as possible. I stopped at this

//...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);

      

In node you can probably use this (haven't tested it though)

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

      

0


source







All Articles