fold_left fold_left - 7 months ago 26
AngularJS Question

AngularJS: spyOn both $timeout and $timeout.cancel

When testing part of an AngularJS Application which uses both

$timeout
and
$timeout.cancel
with Jasmine's
spyOn
method.

describe('<whatever>', function() {

beforeEach(function() {
spyOn(this, '$timeout').andCallThrough();
spyOn(this.$timeout, 'cancel').andCallThrough();
this.createController();
});

it('should <whatever>', function() {
expect(this.$timeout).toHaveBeenCalled();
expect(this.$timeout.cancel).toHaveBeenCalled();
});

});


You should encounter the following error in your application code, which is using what your test injected into it.

TypeError: 'undefined' is not a function (evaluating '$timeout.cancel(timeoutPromise)');

Answer Source

If you were to run console.log(Object.keys(this.$timeout)); in your test suite, you will see the following output;

LOG: ['identity', 'isSpy', 'plan', 'mostRecentCall', 'argsForCall', 'calls', 'andCallThrough', 'andReturn', 'andThrow', 'andCallFake', 'reset', 'wasCalled', 'callCount', 'baseObj', 'methodName', 'originalValue']

$timeout is a function which AngularJS is also decorating—since functions are objects—with a cancel method. Because this isn't that common a thing to do, Jasmine replaces rather than augments $timeout with it's spying implementation - clobbering $timeout.cancel.

A workaround for this is to put the cancel spy back again after $timeout has been overwritten by Jasmine's $timeout spy, as follows;

describe('<whatever>', function() {

  beforeEach(function() {
    spyOn(this.$timeout, 'cancel').andCallThrough();
    var $timeout_cancel = this.$timeout.cancel;
    spyOn(this, '$timeout').andCallThrough();
    this.$timeout.cancel = $timeout_cancel;
    this.createController();
  });

  it('should <whatever>', function() {
    expect(this.$timeout).toHaveBeenCalled();
    expect(this.$timeout.cancel).toHaveBeenCalled();
  });

});