How We Handled the Problem of JS Date/Time Equality
We are using Konacha and Chai on a project and we needed to compare a bunch of time values in our specs. This post describes some of the pain we experienced testing JS Date/Time equality, and what we did about it. As a result of this work, we've released a Chai plugin for datetime assertions that you can find here:
Comparing Dates and Times in JavaScript
Date equality in JavaScript is not based on value.
var t1 = new Date(2013, 4, 30, 16, 5) var t2 = new Date(2013, 4, 30, 16, 5) t1 == t2 // => false t1 === t2 // => false t1.getTime() === t2.getTime() // => true
I was growing very tired of calling getTime() on my expected and actual values. Here is what the specs were looking like before we added custom assertions.
describe('time equality', function() { it('returns true when they are the same time', function() { var actual = new Date(2013, 4, 30, 16, 5), expected = new Date(2013, 4, 30, 16, 5); actual.getTime().should.equal(expected.getTime()); }); it('returns false when they are not the same time', function() { var actual = new Date(2013, 4, 30, 16, 6), expected = new Date(2013, 4, 30, 16, 5); actual.getTime().should.not.equal(expected.getTime()); }); });
Not only does this seem like a lot of repetition, it also provides poor feedback when the assertion fails because we're comparing numeric values instead of the times themselves.
describe('failing time equality', function() { it('fails to have readable failure messages', function() { var actual = new Date(2013, 4, 30, 16, 6), expected = new Date(2013, 4, 30, 16, 5); actual.getTime().should.equal(expected.getTime()); }); });
AssertionError: expected 1369944360000 to equal 1369944300000
chai.Assertion.addChainableMethod('equalTime', function(time) { var expected = time.getTime(), actual = this._obj.getTime(); return this.assert( actual == expected, 'expected ' + this._obj + ' to equal ' + time, 'expected ' + this._obj + ' to not equal ' + time ); }); chai.Assertion.addChainableMethod('equalDate', function(date) { var expectedDate = date.toDateString(), actualDate = this._obj.toDateString(); return this.assert( expectedDate === actualDate, 'expected ' + actualDate + ' to equal' + expectedDate, 'expected ' + actualDate + ' to not equal' + expectedDate ) });
Here are the specs from above with less getTime() noise and better failure messages.
Passing with less getTime() noise
describe('better time equality', function() { it('returns true when they are the same time', function() { var actual = new Date(2013, 4, 30, 16, 5), expected = new Date(2013, 4, 30, 16, 5); actual.should.equalTime(expected); }); it('returns false when they are not the same time', function() { var actual = new Date(2013, 4, 30, 16, 6), expected = new Date(2013, 4, 30, 16, 5); actual.should.not.equal_time(expected); }); });
Failing with Better Message
describe('failing time equality', function() { it('fails to have readable failure messages', function() { var actual = new Date(2013, 4, 30, 16, 6), expected = new Date(2013, 4, 30, 16, 5); actual.should.equalTime(expected); }); });
AssertionError: expected Thu May 30 2013 16:06:00 GMT-0400 (EDT) to equal Thu May 30 2013 16:05:00 GMT-0400 (EDT)
You can also compare dates regardless of their times.
describe('date equality', function() { it('compares dates regardless of their associated times', function() { var actual = new Date(2013, 4, 30, 16, 6), expected = new Date(2013, 4, 30); actual.should.be.equalDate(expected); }); });
Too much detail? Go check out the README and jump in!