Maarten
Maarten

Reputation: 4671

Mocking dates in AngularJS / Jasmine tests

I have a directive that initializes the Date object several times in several functions. When Unit testing the individual functions I can handle stubbing the date like this:

(function (global) {
  var NativeDate = global.Date;

  global.stubDateConstructor = function (fakeDate) {
      global.Date = function () {
          global.Date = NativeDate;
          return fakeDate;
      }
  }
}(this));

// ageInYears()
it("should return the age in years of the person given his/her birthdate", function() {
    stubDateConstructor(new Date('2010/01/01'));
    expect(ageInYears('01-01-1990')).toBe(20);
    stubDateConstructor(new Date('2010/01/01'));
    expect(ageInYears('01-01-1900')).toBe(110);
});

For unittesting the directive itself, which calls the ageInYears and several other similar functions this isn't going to work as I after one call to Date() stubDateConstructor will have reset Date() to the real Date object.

Is there a native way in AngularJS / Jasmine to handle these situations, or should I look into Sinon e.g.?

Upvotes: 37

Views: 26761

Answers (4)

prasanth shenoy
prasanth shenoy

Reputation: 235

angular.mock.TzDate would a better native alternative here. this comes as a helper from angular mocks and truly safe guard your test from the system timezone or any other dependencies

https://docs.angularjs.org/api/ngMock/type/angular.mock.TzDate

this plays well with Jasmine or mocha

Upvotes: 1

Rimian
Rimian

Reputation: 38418

Jasmine (2.2) Clock can mock dates and time.

http://jasmine.github.io/2.2/introduction.html#section-Mocking_the_Date

For example (from the docs):

it("mocks the Date object and sets it to a given time", function() {
  var baseTime = new Date(2013, 9, 23);
  jasmine.clock().mockDate(baseTime);

  jasmine.clock().tick(50);
  expect(new Date().getTime()).toEqual(baseTime.getTime() + 50);
});

Upvotes: 67

ossek
ossek

Reputation: 1648

I was able to mock using a combination of sinon's fake timers to mock the window's timers and angular's mock interval service for angular to recognize time changes . Here, the countDownService under test makes use internally of both javscript Date and angular's normal interval service Something like:

  describe('when start time was 3000 milliseconds and 1001 milliseconds have passed', function() {
    var startTime;
    var elapse;
    beforeEach(function(){
      this.clock = sinon.useFakeTimers();
      startTime = 3000;
      elapse = 1001;
    });

    var elapseMillis = function(intervalMock,sinonClock,millis){
      sinonClock.tick(millis);
      intervalMock.flush(millis);
    };

    it('elapsedMillis + timeRemainingMillis should == startime', 
      inject(function($rootScope,$interval,countdownService) {
        countdownService.startTimer(startTime);
        elapseMillis($interval,this.clock,elapse);
        //jasmine clock does not mock Date
        //see https://github.com/pivotal/jasmine/issues/361
        var elapsedMillis = countdownService.getElapsedMillis();
        var timeRemainingMillis = countdownService.getTimeRemainingMillis();
        expect(elapsedMillis + timeRemainingMillis).toEqual(startTime);
        expect(elapsedMillis).toEqual(elapse);
    }));

    afterEach(function(){
      this.clock.restore();
        startTime = 0;
        elapse = 0;
    });
  });

You'll want to make sure and include sinon js's sinon-timers-1.8.1.js in your karma.conf.js files property, too.

Upvotes: 0

Dave
Dave

Reputation: 6104

A straightforward solution would be to create an Angular Dates service that provides Date objects for you - it might even just have a single method - Dates.now() - that just sends back the current date by returning new Date(). You then use this service whenever something needs to get the current date.

This then allows you to inject a different Dates service when unit testing, for example one that always returns a specific date of your choice when called, rather than the current time.

Upvotes: 12

Related Questions