Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to spy (Jasmine) on Array.prototype methods causes stack overflow

This is pretty odd. Using the testem runner with jasmine2 and the following spec executes (though it correctly flags that there are no expectations):

describe('Spying on array.prototype methods', function(){
  it('should work this way', function(){
    spyOn( Array.prototype, 'push' ).and.callThrough();
    // expect(1).toBe(1);
  });
});

However, add an expect (any expect!) and it causes the stack to overflow with the following message in the testem console: RangeError: Maximum call stack size exceeded. at http://localhost:7357/testem/jasmine2.js, line 980 The html report page gets up to the spec and then hangs without showing any actual results.

Ultimately I'd like to do something like this:

describe('Some structure.method', function(){
  it('does not use native push', function(){
    spyOn( Array.prototype, 'push' ).and.callThrough();
    [].push(1); // actually more like `myStructure.myMethod('test')`
    expect( Array.prototype.push ).not.toHaveBeenCalled();
  });
});

Thanks in advance to anyone who can shed light on this oddity. Can I not spy on native prototypical methods?

like image 969
Gabriel L. Avatar asked Jun 10 '15 23:06

Gabriel L.


1 Answers

When you spy on something jasmine creates a wrapper inorder to track the invocation of that function. Here when you spy on the prototype method basically even the push operation in jasmine itself invokes the spy instead of the actual push method on the array and it causes an infinite loop.

When you call [].push(1) it actually calls the tracker like below:

   spy = function() {
    callTracker.track({ //<-- Calls tracker to track invocation
      object: this,
      args: Array.prototype.slice.apply(arguments)
    });

which in turn calls the call tracker and pushes the call context to its internal tracker array and goes in an recursive loop till the call stack blows out.

this.track = function(context) {
  calls.push(context); //Now this again calls the spy
};

Instead if you spy on the method on array instance, you wont have this issue, since it creates a spy wrapper for the push property of that array instance (or in other words reference (currently inherited from the Array prototype) held by push of that instance gets overwritten by the new function reference of the spy created by jasmine): example:

it('does not use native push', function(){
  var arr = [];
  spyOn(arr, 'push' ).and.callThrough();
  arr.push(1);
  expect(arr.push).toHaveBeenCalledWith(1);
});

But as a real use case (at least i never had to) you could always check for the length of the target array and get the last item to compare against after a specific operation. You probably would never need to spy on native methods ( atleast not an array :) ), instead test against the object of your interest and spy on those target methods.

like image 155
PSL Avatar answered Oct 01 '22 21:10

PSL