Заглушки Date.now() и Math.random()

Я использую Mocha с Sinon для модульного тестирования моих модулей node.js. Я успешно имитировал другие зависимости (другие написанные мной модули), но столкнулся с проблемами, заглушающими нечистые функции (например, Math.random() и Date.now()). Я пробовал следующее (упрощенное, чтобы этот вопрос не был таким локализованным), но Math.random() не был заглушен из-за очевидной проблемы с областью действия. Экземпляры Math независимы между тестовым файлом и mymodule.js.

test.js

var sinon    = require('sinon'),
    mymodule = require('./mymodule.js'),
    other    = require('./other.js');

describe('MyModule', function() {
    describe('funcThatDependsOnRandom', function() {
        it('should call other.otherFunc with a random num when no num provided', function() {
            sinon.mock(other).expects('otherFunc').withArgs(0.5).once();
            sinon.stub(Math, 'random').returns(0.5);

            funcThatDependsOnRandom(); // called with no args, so should call
                                       // other.otherFunc with random num

            other.verify(); // ensure expectation has been met
        });
    });
});

Таким образом, в этом надуманном примере functThatDependsOnRandom() будет выглядеть так:

mymodule.js

var other = require('./other.js');

function funcThatDependsOnRandom(num) {
    if(typeof num === 'undefined') num = Math.random();

    return other.otherFunc(num);
}

Можно ли в этом сценарии заглушить Math.random() Синон?


person Bailey Parker    schedule 09.06.2013    source источник


Ответы (3)


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

Я справился с этим в браузере, создав прокси-объект. Например, вы не можете заглушить объект окна в браузере, поэтому вы можете создать прокси-объект с именем windowProxy. Если вы хотите получить местоположение, вы создаете метод в windowProxy с именем location, который возвращает или устанавливает windowLocation. Затем при тестировании вы издеваетесь над windowProxy.location.

Вы можете сделать то же самое с Node.js, но это не так просто. Простая версия заключается в том, что один модуль не может связываться с частным пространством имен другого модуля.

Решение заключается в использовании модуля mockery. После инициализации mockery, если вы вызовете require() с параметром, который соответствует тому, что вы указали mockery для имитации, это позволит вам переопределить оператор require и вернуть ваши собственные свойства.

ОБНОВЛЕНИЕ: я создал полнофункциональный пример кода. Он находится на Github по адресу newz2000/dice-tdd и доступен через npm. /КОНЕЦ ОБНОВЛЕНИЯ

Документы довольно хороши, поэтому я предлагаю их прочитать, но вот пример:

Создайте файл randomHelper.js с таким содержимым:

module.exports.random = function() {
  return Math.random();
}

Затем в вашем коде, которому требуется случайное число, вы:

var randomHelper = require('./randomHelper');

console.log('A random number: ' + randomHelper.random() );

Все должно работать как обычно. Ваш прокси-объект ведет себя так же, как Math.random.

Важно отметить, что оператор require принимает единственный параметр, './randomHelper'. Нам нужно это отметить.

Теперь в вашем тесте (например, я использую мокко и чай):

var sinon = require('sinon');
var mockery = require('mockery')
var yourModule; // note that we didn't require() your module, we just declare it here

describe('Testing my module', function() {

  var randomStub; // just declaring this for now

  before(function() {
    mockery.enable({
      warnOnReplace: false,
      warnOnUnregistered: false
    });

    randomStub = sinon.stub().returns(0.99999);

    mockery.registerMock('./randomHelper', randomStub)
    // note that I used the same parameter that I sent in to requirein the module
    // it is important that these match precisely

    yourmodule = require('../yourmodule');
    // note that we're requiring your module here, after mockery is setup
  }

  after(function() {
    mockery.disable();
  }

  it('Should use a random number', function() {
    callCount = randomStub.callCount;

    yourmodule.whatever(); // this is the code that will use Math.random()

    expect(randomStub.callCount).to.equal(callCount + 1);
  }
}

И это все. В этом случае наша заглушка всегда будет возвращать 0.0.99999; Вы, конечно, можете изменить его.

person newz2000    schedule 04.12.2014
comment
Отличный ответ. Вы также можете использовать proxyquire вместо издевательства. - person Wtower; 25.11.2016

Вы уверены, что проблема в том, чтобы не издеваться над Math. Кажется, что эта строка не имеет большого смысла:

sinon.mock(other).expects('otherFunc').withArgs(0.5).once();

вы издеваетесь над others в одном модуле, но используете его в другом. Я не думаю, что вы получите издевательскую версию в mymodule.js. С другой стороны, заглушка Math.random должна работать, так как это глобально для всех модулей.

Также взгляните на этот SO для имитации зависимостей в тестах nodeJS.

person Andreas Köberle    schedule 09.06.2013
comment
Поправьте меня, если я ошибаюсь, но я думал, что модули кешируются после того, как их первый запрос. Таким образом, require('./other.js') в наборе тестов и в тестируемом коде должен быть одним и тем же экземпляром. С таким мышлением я предположил, что (как Math.random) издевательство над other в одном изменит объект в другом. Но это, вероятно, не работает, поскольку присваивает новый объект другому вместо замены свойств. Какой-нибудь способ, который вы знаете с Синон, чтобы обойти это? - person Bailey Parker; 10.06.2013

Легко заменить Date.now() на sinon, используя фальшивые таймеры:

Поддельные таймеры предоставляют объект часов для передачи времени, который также можно использовать для управления объектами Date, созданными с помощью new Date(); или Дата.сейчас(); (если поддерживается браузером).

// Arrange
const now = new Date();
const clock = sinon.useFakeTimers(now.getTime());

// Act
// Call you function ...

// Assert
// Make some assertions ...

// Teardown
clock.restore();
person Mohamed Ramrami    schedule 12.05.2018