Search code examples
angularjsjasminekarma-runnerkarma-jasmineangular-mock

Testing $interval in Jasmine/ Karma


I have a simple factory

angular.module('myApp.dice',[]).factory('Dice', ['$interval', function($interval){
    return {
      rollDice: function(){
        return $interval(function(c){
               count++;
          }, 100, 18, false, 0);
      }
    };
}]);

In my test case I have

describe('rolling dice', function(){
      var promise, promiseCalled = false, notify = false;
      beforeEach(inject(function(){
        promise = Dice.rollDice();
        promise.then(function(){
          promiseCalled = true;
        });

      }));

      it('should invoke a promise', inject(function(){
        expect(promise).toBeDefined();
      }));

      it('should set promiseCalled', inject(function($rootScope, $interval){


        expect(promiseCalled).toEqual(true);
      }));
    });

How do I trigger the interval or resolve the promise? to get the test to be true?


Solution

  • See plunker

    You need to use '$interval.flush' from angularjs-mocks and then test for the result. I've taken the liberty to assign count to the dice object because it's undefined in your code sample.

    Is there any reason why $interval is called with invokeApply = false? because then you have to manually call $apply.

    The test case would be:

    describe('rolling dice', function(){
        var $interval, $rootScope, dice;
    
        beforeEach(module('plunker'));
    
        beforeEach(inject(function(_$interval_, _$rootScope_, _Dice_){
          $interval = _$interval_;
          $rootScope = _$rootScope_;
          dice = _Dice_;
        }));
    
        it('should increment count', inject(function(){
          dice.rollDice();
          // Move forward by 100 milliseconds
          $interval.flush(100);
          // Need to call apply because $interval was called with invokeApply = false
          $rootScope.$apply();
          expect(dice.count).toBe(1);
        }));
      });
    

    with factory:

    app.factory('Dice', ['$interval', function($interval){
        var self= {
          count: 0,
          rollDice: function(){
            return $interval(function() {
                  self.count++;
              }, 100, 18, false, 0);
          }
        };
        return self;
    }]);
    

    EDIT: I prefer testing the result of a function call but perhaps you have a use case for testing that a promise function is called so this might be what you're looking for. From the angular docs on $interval it says it returns

    A promise which will be notified on each iteration.

    Keyword being notified. And from the promise API the arguments for then are

    then(successCallback, errorCallback, notifyCallback)
    

    i.e. the third call back function notifyCallback is called for each iteration of $interval. So the test would look something like this:

    it('should set promiseCalled', function(){
      var promiseCalled = false;
      dice.rollDice().then(null, null, function() {
        promiseCalled = true;
      });
      $interval.flush(100);
      $rootScope.$apply();
      expect(promiseCalled).toBe(true);
    });
    

    If you want to test for a resolved promise, then it would look something like this:

    it('should resolve promise', function(){
      var promiseCalled = false;
      var resolvedData = null;
      $q.when('Some Data').then(function(data) {
        promiseCalled = true;
        resolvedData = data;
      });
      $rootScope.$apply();
      expect(promiseCalled).toBe(true);
      expect(resolvedData).toBe('Some Data');
    });
    

    I've updated the plunker with these test cases.