I am trying to test my $watch
listener, however, it's not firing in the test.
Here's the test:
it('should fire $watch when selectedBrands change', function() {
spyOn($scope, 'updateFilters')
$scope.selectedBrands = ['1'];
expect($scope.updateFilters).toHaveBeenCalled();
})
And the controller:
$scope.selectedBrands = [];
$scope.$watch("selectedBrands", function() {
$scope.updateFilters();
}, true);
Finally, the error:
Chrome 35.0.1916 (Mac OS X 10.10.0) ItemsController should fire $watch when selectedBrands change FAILED
Expected spy updateFilters to have been called.
Error: Expected spy updateFilters to have been called.
at null.<anonymous> (/Applications/MAMP/htdocs/cloqet_client/testing/tests/ItemModule/ItemsController_spec.js:62:38)
I tried using $scope.$digest()
but that makes the test pass even without changing $scope.selectedBrands
.
Why is the $watch
not being fired?
I am trying to test my $watch
listener, however, it's not firing in the test.
Here's the test:
it('should fire $watch when selectedBrands change', function() {
spyOn($scope, 'updateFilters')
$scope.selectedBrands = ['1'];
expect($scope.updateFilters).toHaveBeenCalled();
})
And the controller:
$scope.selectedBrands = [];
$scope.$watch("selectedBrands", function() {
$scope.updateFilters();
}, true);
Finally, the error:
Chrome 35.0.1916 (Mac OS X 10.10.0) ItemsController should fire $watch when selectedBrands change FAILED
Expected spy updateFilters to have been called.
Error: Expected spy updateFilters to have been called.
at null.<anonymous> (/Applications/MAMP/htdocs/cloqet_client/testing/tests/ItemModule/ItemsController_spec.js:62:38)
I tried using $scope.$digest()
but that makes the test pass even without changing $scope.selectedBrands
.
Why is the $watch
not being fired?
- Found anything interesting in the answers below ? – gkalpak Commented Jun 20, 2014 at 5:56
3 Answers
Reset to default 3It's correct to use $scope.$digest()
to trigger the digest.
it('should fire $watch when selectedBrands change', function() {
spyOn($scope, 'updateFilters')
$scope.selectedBrands = ['1'];
$scope.$digest();
expect($scope.updateFilters).toHaveBeenCalled();
});
but that makes the test pass even without changing $scope.selectedBrands
The reason is $watch always fires the first time with undefined
even you don't change the value. The current test reflects the correct behavior of your current code. It means that your code has a bug that needs to be fixed.
To fix this, try fixing your code:
$scope.$watch("selectedBrands", function(value) {
if (value){ //check for undefined fired the first time.
$scope.updateFilters();
}
}, true);
DEMO
Tests run outside of the Angular-context, so there is no automatic $digest cycle happening.
$watch()
is not fired because you need to first invoke a $digest cycle manually.
You should also keep in mind that every $watch callback is executed once to initialize the value.
That is why your $scope.updateFilter()
gets called even without changing selectedBrands
.
Quoting the docs:
"After a watcher is registered with the scope, the listener
fn is called asynchronously (via $evalAsync) to initialize the watcher."
The proper way to test this, is to manually invoke a $digest cycle (so the the $watch is initiaized), then change selectedBrands
and finally invoke a secod $digest cycle to "digest" that change.
E.g.:
it('should fire $watch when selectedBrands change', function() {
$scope.$digest();
spyOn($scope, 'updateFilters');
$scope.selectedBrands = ['1'];
$scope.$digest();
expect($scope.updateFilters).toHaveBeenCalled();
});
See, also, this short demo.
you need a digest process to get fired, that's when watchers do checks
it('should fire $watch when selectedBrands change', function() {
spyOn($scope, 'updateFilters')
$scope.selectedBrands = ['1'];
$scope.$digest()
expect($scope.updateFilters).toHaveBeenCalled();
})