I have the following test for a service that internally uses $timeout:
describe('Beat', function(){
var nagBeat, $timeout;
beforeEach(module('nag.beat'));
beforeEach(inject(function($injector) {
nagBeat = $injector.get('nagBeat');
$timeout = $injector.get('$timeout');
}));
it('should be able to add and remove beat', function() {
var test = 0;
nagBeat.add('increment', function() {
test += 1;
}, 50);
//wait and flush timeout 2 times
waits(50);
$timeout.flush();
waits(50);
$timeout.flush();
expect(test).toEqual(2);
expect(nagBeat.activeBeatsCount()).toEqual(1);
nagBeat.remove('increment');
waits(50);
$timeout.flush();
expect(nagBeat.activeBeatsCount()).toEqual(0);
});
});
Now everything works fine until I get to the last set of waits/$timeout.flush(). This test fails with the error:
No deferred tasks to be flushed
However that is the desired result, the timeout should no longer exist after I call nagBeat.remove().
Now maybe there is a way to test this functionality but when I remove the throw Error() from angular-mocks.js and run the test again it passes fine.
If there is a better way to test this type of functionality, please let me know. If not I would be happy to make this change.
If you just want to make sure the $timeout queue is empty, you could do this:
expect(function() {$timeout.flush();}).toThrow();
Thanks, that does work, I guess I will close this issue.
@ryanzec, what's the use case for throwing an error instead of a noop when there's nothing to flush?
@ryanzec
I think this issue should be reopened.
I have a directive that, depending on its configuration, may or may not use:
$timeout( function() { ... } );
This is used so to way for the DOM to render (there are various CSS properties I need to read from the DOM and some positioning logic that requires a live DOM).
The test methods has no simple way of knowing whether there will be a deferred task or not, without me introducing quite a few if conditions to the tests (or alternatively, replicate all tests 3 times to account for 3 different possible config options).
I wish we could at least have the API expose a method telling whether or not there are any deferred tasks. Seems like an easy job, simply add to angular-mocks
:
$delegate.needsFlushing = function() {
return $browser.deferredFns.length > 0;
};
At the moment I'm using this code instead:
try {
$timeout.verifyNoPendingTasks();
} catch ( aException ) {
$timeout.flush();
}
@Izhaki why not just do $timeout.flush(1000)
@lgalfaso
This seems to work, but I believe it rely on the assumption that any ongoing timeout will take less than a second to complete in the code itself?
@Izhaki yes, it has that assumption. Now, if a test is not able to control _everything_ that surrounds what is being tested, then the test has the potential to be flaky, so changing the default behavior of $timeout.flush()
is not a good idea
+1 @lgalfaso
If working with $httpBackend, the solution in the second comment does not work. I'm testing code that makes an HTTP request after a timeout, so I want to check that it's finished by doing
it ('should not make any more http requests', function() {
expect(function() {$timeout.flush();}).toThrow();
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
this causes the test to fail with
Error: [$rootScope:inprog] $digest already in progress
thrown by verifyNoOutstandingExpectation()
@chrisjensen can you create a plunker with this issue?
+1, IMO, the $timeout.flush()
should be noop when nothing to flush.
But keep in mind that in CoffeeScript "fixing" it is as simple as
try $timeout.flush()
This bug just caught me as well. Maybe if you are concerned about backwards compatibility, there can be another way to call $timeout.flush
that won't throw. Perhaps:
$timeout.flush(delay, { noThrow: true });
There is currently no way to clear the timeout queue with a noop if the queue is empty. I'm not aware of a way to check the length of the queue, either.
Most helpful comment
This bug just caught me as well. Maybe if you are concerned about backwards compatibility, there can be another way to call
$timeout.flush
that won't throw. Perhaps:There is currently no way to clear the timeout queue with a noop if the queue is empty. I'm not aware of a way to check the length of the queue, either.