39

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?

3 Answers 3

84
+50

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.

Sign up to request clarification or add additional context in comments.

4 Comments

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?
Correct, take a look at the code on Github here and here.
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
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
10

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.

2 Comments

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?
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.
4

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();
  });

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.