Bailey Parker
Bailey Parker

Reputation: 15905

Stubbing Date.now() and Math.random()

I'm using Mocha with Sinon to unit test my node.js modules. I've successfully mocked other dependencies (other modules that I've written), but I've run into problems stubbing non-pure functions (like Math.random() and Date.now()). I've tried the following (simplified so that this question isn't so localized), but Math.random() was not stubbed because of an obvious scope problem. The instances of Math are independent between the test file and 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
        });
    });
});

So in this contrived example, functThatDependsOnRandom() would look like:

mymodule.js

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

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

    return other.otherFunc(num);
}

Is it possible to stub Math.random() in this scenario with Sinon?

Upvotes: 10

Views: 9485

Answers (3)

Mohamed Ramrami
Mohamed Ramrami

Reputation: 12691

It is easy to stub Date.now() with sinon by using Fake timers :

Fake timers provide a clock object to pass time, which can also be used to control Date objects created through either new Date(); or Date.now(); (if supported by the browser).

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

// Act
// Call you function ...

// Assert
// Make some assertions ...

// Teardown
clock.restore();

Upvotes: 1

newz2000
newz2000

Reputation: 2640

yes, this is an old question but it is valid. Here is an answer that works, though I'd love to hear suggestions on how to make it better.

The way I've dealt with this in the browser is to create a proxy object. For example, you can't stub the window object in the browser so you can create a proxy object called windowProxy. When you want to get the location you create a method in windowProxy called location that returns or sets windowLocation. Then, when testing, you mock windowProxy.location.

You can do this same thing with Node.js, but it doesn't work quite as simply. The simple version is that one module can't mess with another module's private namespace.

The solution is to use the mockery module. After initializing mockery, if you call require() with a parameter that matches what you told mockery to mock, it will let you override the require statement and return your own properties.

UPDATE: I've created a fully functional code example. It is on Github at newz2000/dice-tdd and available via npm. /END UPDATE

The docs are pretty good, so I suggest reading them, but here's an example:

Create a file randomHelper.js with contents like this:

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

Then in your code that needs a random number, you:

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

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

Everything should work like normal. Your proxy object behaves in the same way as Math.random.

It is important to note that the require statement is accepting a single parameter, './randomHelper'. We'll need to note that.

Now in your test, (I'm using mocha and chai for example):

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

And that is it. In this case, our stub will always return 0.0.99999; You can of course change it.

Upvotes: 9

Andreas Köberle
Andreas Köberle

Reputation: 110892

Are you sure that not mocking Math is the problem. It seems that this line make not much sense:

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

you mock others in one module but use it in another one. I dont think that you will get the mocked version in mymodule.js. On the other hand stubbing Math.random should work, as this is global for all modules.

Also take a look at this SO for mocking dependencies in nodeJS tests.

Upvotes: 0

Related Questions