I am testing a directive. The directive has a watch on a scope variable, but the watch is never called. Putting the $watch
call directly inside the unit test works. Why isn't the $watch
in the directive not called?
Thanks!
Here's my original test. The directive has a watch in it, looking at 'reEvalCreateDate'
. It's never called and the date string is always empty.
it('should inject a date string', function()
{
scope.reEvalCreateDate = reEvalCreateDateAsNumber;
expect(elm.text()).toBe('');
scope.$digest();
expect(elm.text()).toBe(new Date(reEvalCreateDateAsNumber).toUTCString());
});
As a test, I put the watch within the unit test. It is correctly called and the test passes.
it('should inject a date string', function()
{
scope.$watch('reEvalCreateDate', function(newValue, oldValue)
{
var date = new Date(newValue);
scope.dateString = date.toUTCString();
dump(scope.dateString)
});
scope.reEvalCreateDate = reEvalCreateDateAsNumber;
expect(elm.text()).toBe('');
scope.$digest();
expect(elm.text()).toBe(new Date(reEvalCreateDateAsNumber).toUTCString()); //This passes!
});
Here's the directive code with the watch statement in it.
link: function(scope, element, attrs)
{
scope.$watch('show',function(shouldShow){
console.log('should show reeval timer? ' + shouldShow);
if(shouldShow) {
$(element).fadeIn('slow');
}
else if(shouldShow === false){
$(element).fadeOut('slow');
}
});
//this statement's never called when testing!!
scope.$watch('reEvalCreateDate', function(newValue, oldValue)
{
var date = new Date(newValue);
scope.dateString = date.toUTCString();
});
}
Here's all the init code for the test.
var elm, scope;
var reEvalCreateDateAsNumber = 1359487598000;
beforeEach(inject(function($rootScope, $compile)
{
// we might move this tpl into an html file as well...
elm = angular.element(
'<reeval-timer show="showTimePopup" re-eval-create-date="reEvalCreateDate" class="re-eval-timer" ng-cloak >' +
'{{dateString}}' +
'</reeval-timer>');
scope = $rootScope.$new();
$compile(elm)(scope);
scope.$digest();
}));
Per the comment, I don't think that I am setting the scope on the directive; I don't see anything on the API that allows me to do that. I've tried several permutations of the $compile
call, attempting to set the scope on it:
create an element from html, pass it to compile. No luck.
elm = angular.element(
'<div>' +
'<tabs>' +
'<pane title="First Tab">' +
'first content is {{first}}' +
'</pane>' +
'<pane title="Second Tab">' +
'second content is {{second}}' +
'</pane>' +
'</tabs>' +
'</div>');
scope = $rootScope;
$compile(elm)(scope);
and by creating an element first
var element = $compile('<p>{{total}}</p>')(scope);
If the issue is that I'm just not setting the scope on the directive, the error would likely be in my beforeEach
, I think. Here it is:
describe('Reeval Timer', function()
{
var elm, scope,element;
var reEvalCreateDateAsNumber = 1359487598000;
beforeEach(inject(function($rootScope, $compile)
{
// we might move this tpl into an html file as well...
elm = angular.element(
'<reeval-timer show="showTimePopup" re-eval-create-date="reEvalCreateDate" class="re-eval-timer" ng-cloak >' +
'</reeval-timer>');
scope = $rootScope.$new();
element = $compile(elm.contents())(scope);
scope.$digest();
}));
....
I just ran into a similar issue while testing a directive with isolate scope.
The reason it wasn't called is that $digest
"processes all of the watchers of the current scope and its children" (http://docs.angularjs.org/api/ng.$rootScope.Scope). Now, you would think that a directive's bi-directionally bound variables (defined with "=") would be watching the test's scope, but I found that my $watch
wasn't being called. Is this a bug in angular?
My solution was to wrap all the testing code after the scope.$digest()
into a $timeout
, e.g:
inject(function($timeout) {
// change test scope variable
scope.$digest();
$timeout(function() {
// assertions
expect(...)
}, 0);
}
Edit: Also see Unit testing an AngularJS directive that watches an an attribute with isolate scope
I was experiencing what seems like the same problem. In my case, my directive had a templateUrl
set rather than a hardcoded template
, so in my unit test I also had to set up a mocked http request by calling:
$httpBackend.expectGET('/template.html').respond('<div></div>');
Everything else looked the same as your example, and my watch wasn't being called.
Then I remembered when using $httpBackend
for mocking the request, you have to call $httpBackend.flush()
like so:
$compile(elem)(scope);
$httpBackend.flush();
As soon as the flush call was hit, my watch was called.
Hope this helps....
Jason
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