Reputation: 1304
I have a series of unit tests. One of the controllers is setting a $timeout
promise which executes every 1000 ms (it invokes its own function, so it behaves like an $interval, continues executing every 1 second as long as the controller is being viewed). However, when the controller is destroyed (user navigates to another view), the $timeout.cancel
is called accurately, thus the self-invoking ceases to continue.
But when running unit tests, for some reason after the test for this controller is executed, the $destroy is never triggered.
So when second unit test is run (in this case a unit test for a directive), which also uses $timeout.flush(...)
, it is activating the $timeout promise in the unassociated controller!
But I don't know how to force the controller to $destroy after its unit tests are completed, or how to nuke $timeout to remove/cancel all promises before starting the other unit tests.
As a result, it's causing a conflict between the two unit tests, where one has unexpected behavior that is causing it to fail.
Any suggestions?
[Edit] Solution: By adding scope.$destroy()
to all our controller and directive unit tests, we went from 30 seconds of unit test execution time to 6 seconds! And the $timeout conflict no longer appears.
afterEach(function () {
scope.$destroy(); //directive, or controller: $scope.$destroy();
});
Upvotes: 1
Views: 953
Reputation: 703
You can inject $timeout
service when instantiating controllers and use the timeout.flush()
to get the callback executed and test.
Updated Plunker with real $timeout service.
Upvotes: 1
Reputation: 1402
I would suggest to avoid creating real timeouts while unit-testing. That would require injecting mock of $timeout
service when instantiating controllers, but provides more control over asynchronous operations based on timeouts during unit-testing. In simplest case when there is no need to test timeout callbacks $timeout
mock can be very simple (example uses Jasmine):
describe('MainCtrl', function() {
var $scope = null;
var ctrl = null;
var mockedTimeout = function() {
console.log('Timeout is mocked');
}
mockedTimeout.cancel = function() {
console.log('Timeout is cancelled');
}
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {
$scope: $scope,
$timeout: mockedTimeout
});
}));
});
Here is plunker that shows complete test setup in action
Upvotes: 1