Fake today in unit tests

AD, please don't block.

If the app to which you are working is strongly influenced by the date, you definitely need a simple and consistent way to create a fake today, to have significant unit tests.

I do not want to make you wait… this is the way I mock the Date constructor.

function fakeDate(fakeToday) {
  var OldDate = Date;
  spyOn(window, 'Date').and.callFake(function() {
    var args = Array.prototype.slice.call(arguments);
    if (args.length === 0) {
      return new OldDate(fakeToday);
    }

    var _factoryFn = Function.prototype.bind.apply(OldDate, [null].concat(args));
    return new _factoryFn();
  });
}

What’s happening there?

Let’s try to understand that code.

First of all I made a copy of the original Date function, and through the jasmine’s framework spyOn method, I created a spy on it.

var OldDate = Date;
spyOn(window, 'Date').and.callFake(function() {
  // this is executed everytime Date is called
});

Now there are two cases:

  • Date was called without any parameter.

    In this case it is supposed to return the date of today; so in this case we have to use the fakeToday variable in the closure scope, to create a fake today date. In order to create the date it’s not possible to use the Date constructor, but we can use its copy, previously saved in the OldDate variable.

if (args.length === 0) {
  return new OldDate(fakeToday);
}
  • Date was called with some parameters; it accepts one argument, or a comma-separated list of arguments.

    I used the function’s bind method on the OldDate constructor, called via the apply method, that permits to pass the parameters as an array.

var tmp = [null].concat(args);
var _bind = Function.prototype.bind;

var _factoryFn = _bind.apply(OldDate, tmp);
return new _factoryFn();

// ECMAScript 6 will make this even simpler
// via spread operator (...)

return new OldDate(...args);

Usage

Let’s take a look now at how to use our fakeDate function.

function Calendar() {

  this.today = this.selectedDay = new Date();

  on('NextWeekSelected', function() {
    // addDaysToDate add x days to the date passed as first parameter
    this.selectedDay = addDaysToDate(this.today, days);
  }.bind(this));

}

Then the unit test for the addDaysToDate method looks like:

describe('addDaysToDate', function() {
  it('should be aware that February sometimes has 29 days', function() {

    // create the fake date
    fakeDate('2016-02-26');

    var instance = new Calendar();
    // now: instance.today is February 26 2016

    trigger('NextWeekSelected');

    // since 29 February exists:
    // selectedDay is 4 March 2016;
    // set the expectations!

    expect(this.selectedDay.getDate()).toBe(4);
    expect(this.selectedDay.getMonth()).toBe(2);

  });
});