How to test code that uses moment.js
05 November, 2018
I'm going to talk about unit tests here—not any other kind of tests.
In my opinion, unit tests should meet the following criteria:
- They should run consistently on both the developer’s machine and a remote server.
- They should be independent of timezones.
- Each class (or component) should be tested independently of external libraries.
- Clarification: All external dependencies should be mocked.
These criteria can make testing difficult when dealing with external libraries, especially ones that manipulate time.
First, regardless of what your code does, you don't want to test third-party libraries or other components outside the current unit. For example, we don't want to test how format()
or duration()
methods from moment.js
work. We assume Moment is a well-tested library. (If we want to verify that assumption, we can write separate test files just for Moment.js.)
So what does this mean for our approach? It means we need to mock the third-party library rather than use its original implementation.
Should our mock reproduce the exact functionality? Absolutely not. We only need to verify that the correct parameters are passed to the methods. In the case of Moment.js, we can create a wrapper that mimics the API.
Here's an example using the format()
method:
import moment from 'moment';
const timeMoment = moment(1540500979000);
timeMoment.format('HH:mm:ss, YYYY-MM-DD');
And another example with the duration()
method:
import moment from 'moment';
moment.duration(diffTimestamp).asDays();
We can replace this with a mock that captures input without relying on real behavior:
function MomentMock(time) {
if (this instanceof MomentMock) {
this._time = time;
} else {
return new MomentMock(time);
}
this.format = (format) => `${this._time} [${format}]`;
this.utc = MomentMock;
}
MomentMock.duration = (time) => ({
asDays: () => `${time} asDays`,
});
export default MomentMock;
This approach is especially helpful when the time parameter is a primitive, such as a timestamp. It ensures we're only testing whether the correct timestamp and format string are passed—nothing more. We also eliminate concerns about the machine's timezone, since we avoid using Date()
objects entirely.
You might also be interested in the following posts:
I have already written a post about mocking moment.js for tests. Today let's talk about how to use mocks in a specific tests framework - Jest. Jest was developed by Facebook and was based on another tests library called Jasmine. It meant that Jest used widely popular test syntax and could be adapted very easily.
It's known that moment.js is a widely used library for date and time manipulations. Obviously it is the case because of the limitations of standard JavaScript API, but today we're not going to judge standard language features. Let's accept the insufficiency of built-in tools and investigate whether or not I can replace moment.js with a smaller library.