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

1

7

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?

Friable answered 10/6, 2015 at 23:45 Comment(0)
P
7

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.

Purnell answered 11/6, 2015 at 0:5 Comment(1)
Ah, thanks much! That makes perfect sense. Actually I was trying to write a spec that would basically make sure a student attempting a coding challenge did not use the native methods on ANY object (i.e., in their own solution code) — a very contrived case, not really a production code test spec so much as an exam tool. I cannot really imagine when I'd need to spy on native methods in a normal program.Friable

© 2022 - 2024 — McMap. All rights reserved.