Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 826
Bailey Parker Avatar asked Jun 09 '13 16:06

Bailey Parker


People also ask

What is stubbing in JS?

What are stubs? While spies wrap existing functionality, stubs allow us to replace the functionality completely. The original function won't run anymore, but rather our new stub that simply returns a result. This is especially handy if we want to test async stuff that makes use of things like jQuery's AJAX methods.

How do you mock a constructor like a new date?

Mock Date object js const RealDate = Date; beforeEach(() => { global. Date. now = jest. fn(() => new Date('2019-04-22T10:20:30Z').

What is stubbing in node JS?

Stubs are functions or programs that affect the behavior of components or modules. Stubs are dummy objects for testing. Stubs implement a pre-programmed response.


2 Answers

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.

like image 109
newz2000 Avatar answered Nov 10 '22 07:11

newz2000


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();
like image 1
Mohamed Ramrami Avatar answered Nov 10 '22 07:11

Mohamed Ramrami