Попытка шпионить (Jasmine) на методы Array.prototype вызывает переполнение стека

Это довольно странно. С использованием testem бегун с jasmine2 и выполняется следующая спецификация (хотя она правильно отмечает, что ожиданий нет):

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

Тем не менее, добавить expect (любой expect!) и это приводит к переполнению стека следующим сообщением в testem приставка: RangeError: Maximum call stack size exceeded. at http://localhost:7357/testem/jasmine2.js, line 980 HTML-страница отчета попадает в спецификацию, а затем зависает, не показывая никаких реальных результатов.

В конечном итоге я хотел бы сделать что-то вроде этого:

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

Заранее спасибо всем, кто может пролить свет на эту странность. Могу ли я не шпионить за родными прототипами?

1 ответ

Решение

Когда вы шпионите за чем-то, Жасмин создает порядок оболочки, чтобы отслеживать вызов этой функции. Здесь, когда вы шпионите за методом-прототипом, в принципе, даже операция push в самом jasmine вызывает spy вместо фактического метода push в массиве, и это вызывает бесконечный цикл.

Когда вы звоните [].push(1) он на самом деле вызывает трекер, как показано ниже:

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

который, в свою очередь, вызывает трекер вызовов и помещает контекст вызова в свой внутренний массив трекеров и идет по рекурсивному циклу, пока стек вызовов не разрушится.

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

Вместо этого, если вы шпионите за методом на экземпляре массива, у вас не возникнет этой проблемы, поскольку он создает оболочку-шпион для свойства push этого экземпляра массива (или, другими словами, ссылки (в настоящее время унаследованной от прототипа Array), хранящейся в push этого экземпляра перезаписывается новой ссылкой на функцию шпиона, созданного jasmine): пример:

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

Но как реальный вариант использования (по крайней мере, мне никогда не приходилось), вы всегда можете проверить длину целевого массива и получить последний элемент для сравнения после определенной операции. Вам, вероятно, никогда не понадобится шпионить за нативными методами (по крайней мере, не за массивом:)), а вместо этого протестировать объект, который вас интересует, и следить за этими целевыми методами.

Другие вопросы по тегам