Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To "Fake" Date/Time For Testing Mongoose Models

Beforehand, thank you for any help/advice you can provide!

What I'm Trying To Accomplish

I'm trying to find an elegant way to test the date/time when creating an instance of a Mongoose model.

I'd like to make sure that the time that is stored is the correct time.

My model currently looks like this:

const messageSchema = mongoose.Schema({
  user: { type: String, required: true },
  message: { type: String, required: true },
  created: { type: Date, default: Date.now },
});

const Message = mongoose.model('Message', messageSchema);

I import this model into a mocha test suite, where I'm trying to run a test along the lines of:

const now = {Date message was created}
it('check time matches time created', () => {
  expect(message.created).to.equal(now);
});

What I've Tried So Far

The way I tried to accomplish this was using Sinon's Fake timers functionality.

So my test case looks like this:

describe('creating new message', () => {
  let clock;
  let message;
  let now;

  before(() => {
    clock = sinon.useFakeTimers();
    clock.tick(100);
    message = new Message({
      user: 'Test User',
      message: 'Test Message',
    });
    // Time the message was created
    now = Date.now();
    clock.tick(100);
  });

  it('check time matches time created', () => {
    expect(message.created).to.equal(now);
  });
});

Why I Assume This Doesn't Work

I believe that this doesn't work because the Date.now function which is passed as the default for the Mongoose model is isolated from the Sinon fake timer (the fake timer is in the test file, and the model is imported from another file).

Thank you again!

like image 665
Brandon Yanofsky Avatar asked Dec 20 '17 22:12

Brandon Yanofsky


1 Answers

Solution

Just wrap Date.now in an anonymous function, just like that:

function() { return Date.now(); }

Or an ES6 version

() => Date.now()

So the Schema would become something like:

const messageSchema = mongoose.Schema({
   user: { type: String, required: true },
   message: { type: String, required: true },
   created: { type: Date, default: () => Date.now() },
});

why it works?

Because when you do sinon.useFakeTimers(), what sinon does at the back is to override the global property Date.

And calling Date is the same than calling global.Date.

When you pass Date.now to mongoose, you're essentially passing the Node internal method referenced by global.Date, and mongoose will call this method, without accessing the global.Date reference anymore.

But, with my solution, we're passing a method that, when called, accesses the reference of global.Date, which now is stubbed by Sinon.

In order to see this behavior in practice, you can do in Javascript something like:

var nativeDate = Date; // accessing global.Date
Date = { now: () => 1 }; // overrides global.Date reference to a entirely new object
console.log(Date.now()); // now it outputs 1
console.log(nativeDate.now()); // outputs current date, stub doesn't work here, because it's calling the javascript native Date method, and not global.Date anymore
like image 199
daymannovaes Avatar answered Sep 29 '22 08:09

daymannovaes