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?
This '$timeout' service of AngularJS is functionally similar to the 'window. setTimeout' object of vanilla JavaScript. This service allows the developer to set some time delay before the execution of the function.
The $timeout service can be used to call another JavaScript function after a given time delay. The $timeout service only schedules a single call to the function. For repeated calling of a function, see $interval later in this text.
var customTimeout = $timeout(function () { // arbitrary code }, 55); $timeout. cancel(customTimeout); The same applies to “$interval()”. To disable a watch, just call it.
setTimeout in order to display an alert message after a timeout of at least 2000 milliseconds. Angular $timeout is a wrapper written for window. setTimeout in form of a try catch block which throws exceptions via $exceptionHandler service. $timeout accepts the function to be delayed, delay time, a boolean to invoke $.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With