AngularJS $timeout function not executing in my Jasmine specs

18,462

Solution 1

According to the Angular JS documentation for $timeout, you can use $timeout.flush() to synchronously flush the queue of deferred functions.

Try updating your test to this:

it('should do stuff', function() {
  expect($scope.stuffDone).toBeFalsy();
  $scope.doStuff();
  expect($scope.stuffDone).toBeFalsy();
  $timeout.flush();
  expect($scope.stuffDone).toBeTruthy();
});

Here is a plunker showing both your original test failing and the new test passing.

Solution 2

As noted in one of the comments, Jasmine setTimeout mock is not being used because angular's JS mock $timeout service is used instead. Personally, I'd rather use Jasmine's because its mocking method lets me test the length of the timeout. You can effectively circumvent it with a simple provider in your unit test:

module(function($provide) {
  $provide.constant('$timeout', setTimeout);
});

Note: if you go this route, be sure to call $scope.apply() after jasmine.Clock.tick.

Solution 3

As $timeout is just a wrapper for window.setTimeout, you can use jasmines Clock.useMock() which mocks the window.setTimeout

  beforeEach(function() {
    jasmine.Clock.useMock();
  });

  it('should do stuff', function() {
    $scope.doStuff();
    jasmine.Clock.tick(251);
    expect($scope.stuffDone).toBeTruthy();
  });
Share:
18,462
Esa Toivola
Author by

Esa Toivola

Learning JavaScript.

Updated on July 24, 2022

Comments

  • Esa Toivola
    Esa Toivola almost 2 years

    I'm trying to test my AngularJS controller with Jasmine, using Karma. But a $timeout which works well in real-life, crashes my tests.

    Controller:

    var Ctrl = function($scope, $timeout) {
      $scope.doStuff = function() {
        $timeout(function() {
          $scope.stuffDone = true;
        }, 250);
      };
    };
    

    Jasmine it block (where $scope and controller have been properly initialized):

    it('should do stuff', function() {
      runs(function() {
        $scope.doStuff();
      });
      waitsFor(function() { 
        return $scope.stuffDone; 
      }, 'Stuff should be done', 750);
      runs(function() {
        expect($scope.stuffDone).toBeTruthy();
      });
    });
    

    When I run my app in browser, $timeout function will be executed and $scope.stuffDone will be true. But in my tests, $timeout does nothing, the function is never executed and Jasmine reports error after timing out 750 ms. What could possibly be wrong here?

  • Esa Toivola
    Esa Toivola about 11 years
    Thanks. Should've RTFM. It seems that in Jasmine tests ngMocks module is loaded and the mocked $timeout actually never calls window.setTimeout - am I correct?
  • rtcherry
    rtcherry about 11 years
    Correct, take a look at the code on Github here and here.
  • nabeelfarid
    nabeelfarid over 10 years
    How do I reverse it back to the angular timeout mock, as I have other tests where I don't want to use jasmine mock?
  • chas s.
    chas s. about 10 years
    This is for the Jasmine 1.x API. For Jasmine 2.x you would run jasmine.clock().install() in your beforeEach, jasmine.clock().uninstall() in your afterEach and jasmine.clock().tick(251) in your it function.
  • rtcherry
    rtcherry over 9 years
    So those Github links were referring to master and are no longer useful. Here are the same links against that version for those that are curious: here and here
  • HipsterZipster
    HipsterZipster almost 9 years
    Is it possible to switch back to using timeout.flush(1000) in any of those tests? It seems that using jasmine.clock().tick(1000) isn't a perfect substitute in some cases.
  • Basavaraj Sonnad
    Basavaraj Sonnad over 5 years
    This helps. But i had to call done() in the end to mark end of test case. Otherwise jasmine waits for 5sec(default) and fails as done() is not called. Hope this helps