How can I test date-fns formatDistanceToNow with Jest

Viewed 14

I'm trying to write a unit test with Jest for a function that is using the date-fns formatDistanceToNow function to display human readable distance for today. So if the date is today, it should show for example "in about 5 hours" but if the date is tomorrow just show "Tomorrow". Now it depends on the time, the test is running if "in 5 hours" is still today or already tomorrow.

How can I set a fixed date and time the test is running in? I guess formatDistanceToNow is using something like Date.now() as a reference which is using the system time.

Here is an example.

date-util.js

import { isToday, isTomorrow, formatDistanceToNow } from 'date-fns';

export function formatDate(date) {
  if (isToday(date)) {
    return formatDistanceToNow(value, { addSuffix: true });
  }

  if (isTomorrow(value)) {
    return 'Tomorrow';
  }
}

date-util.spec.js

describe('date-util', () => {
  it('should show today date', () => {
    const today = new Date();
    
    expect(formatDate(today)).toBe('Today');
    // plus 5 minutes
    let testDate = new Date(today.getTime() + 5 * 60000);
    expect(formatDate(testDate)).toBe('in 5 minutes');
    // minus 5 minutes
    testDate = new Date(today.getTime() - 5 * 60000);
    expect(formatDate(testDate)).toBe('5 minutes ago');
    // plus 4 hours and 20 minute
    testDate = new Date(today.getTime() + 260 * 60000);
    expect(formatDate(testDate)).toBe('in about 4 hours');
    // minus 2 hours and 1 minute
    testDate = new Date(today.getTime() - 121 * 60000);
    expect(formatDate(testDate)).toBe('about 2 hours ago');
  });

  it('should show tomorrow date', () => {
    const tomorrow = new Date();
    tomorrow.setDate(tomorrow.getDate() + 1);

    expect(formatDate(tomorrow)).toBe('Tomorrow');
    // 5 minutes ahead
    let testDate = new Date(tomorrow.getTime() + 5 * 60000);
    expect(formatDate(testDate)).toBe('Tomorrow');
    // minus 5 minutes
    testDate = new Date(tomorrow.getTime() - 5 * 60000);
    expect(formatDate(testDate)).toBe('Tomorrow');
  });
});

In the test I'm adding 4 hours and 20 minutes. If the test runs at 8pm, it won't show "in about 4 hours" because it's already tomorrow.

2 Answers

Jest provides a solution to set a fixed date for the date object in all tests. I added this to the test to make it work independent of the daytime. The date being used as "now" in all tests is today at 12:00.

import { set } from 'date-fns';

describe('date-util', () => {
  beforeAll(() => {
    const today = set(new Date(), { hours: 12, minutes: 0, seconds: 0, milliseconds: 0 });
    jest.useFakeTimers({ now: today });
  });

  afterAll(() => {
    jest.useRealTimers();
  });

  it('should show today date', () => {
    // tests
  });

  it('should show tomorrow date', () => {
    // tests
  });
});

date-fns is a functional library so it will not alter the state of an object.

    const now = new Date();
    const tomorrow = setDate(now, getDate(now) + 1);

Edit: Your solution is the better one because doing it this way, if you're on the last day of the month I'm not sure if date-fns will roll over to the first day of the next month or circle back to the first day of the current month.

Related