Mocking current time with Jasmine

October 1st 2021 Angular Unit Testing

In an Angular application, I wanted to test a functionality that depends on the current time (by calling Date.now()). Jasmine has great built-in support for spying on methods, so my first approach was to create a spy for Date.now() that would return different values during testing. And it worked. It was not until later that I realized there was an even more elegant way to do this.

Here is a simplified version of the code I wanted to test:

export class Timer {
  private endTime: number;

  public get secondsRemaining(): number {
    if (!this.active) {
      return this.duration;
    }

    return Math.max(0, Math.floor((this.endTime - Date.now()) / 1000));
  }

  constructor(
    private readonly duration: number,
    private readonly active: boolean
  ) {
    this.endTime = Date.now() + duration * 1000;
  }
}

The most important part is a property that acts as a timer, returning a decrementing value as time passes. To test this, I needed to take control of the values returned by Date.now(). This allowed me to simulate different scenarios.

Jasmine's support for testing time-dependent code is available in the form of methods in the Clock class. To use them in your tests, you must first install (and later uninstall to restore the original behavior) this mocked clock implementation:

beforeEach(() => {
  jasmine.clock().install();
});

afterEach(() => {
  jasmine.clock().uninstall();
});

The method I was most interested in is mockDate(). It sets the value returned by Date.now(). To reduce the amount of repetitive code in each test, I decided to initialize it to a fixed value:

describe('Timer', () => {
  const startingTime = Date.now();

  beforeEach(() => {
    jasmine.clock().install();
    jasmine.clock().mockDate(new Date(startingTime));
  });

  afterEach(() => {
    jasmine.clock().uninstall();
  });

  // ...
}

In individual tests, Date.now() would now always return the same fixed value. If I wanted time to pass, I could call mockDate() again to return a different value:

it("should return decremented duration as time passes", () => {
  const timer = new Timer(5, true);

  jasmine.clock().mockDate(new Date(startingTime + 1000));

  expect(timer.secondsRemaining).toBe(4);
});

This way I could easily test all scenarios I was interested in.

I have put a sample project with a full test suite for the Timer class above in my GitHub repository.

Jasmine's spies are a great tool for mocking external dependencies in tests. However, if you are testing functionality that depends on the passage of time, the Clock class and its methods are usually better suited for the task.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License