admin管理员组

文章数量:1424902

I am having trouble writing unit tests for some of my Angular directives. Particularly, those that use jQuery inside the directive. I have contrived an minimal example below that illustrates my issue.

This silly directive binds the click event to the element. When clicked, it hides the element. According to Angular, elements passed into directives will be wrapped as jQuery elements. if jQuery is available it will use jQuery, otherwise it will use Angular's jQuery Lite. Indeed, if I use this directive in a browser with jQuery included, the directive works and will hide the clicked element.

angular.module('myApp').directive('clickhide', function() { return {
    link: function(scope, element, attrs) { 
        element.bind('click', function(e) {
            element.hide();
            scope.$digest();
        });
    }
}});

Here is the Karma unit test spec for this directive:

describe('clickhide', function() {
    var scope, elm;

    beforeEach(module('MyApp'));

    beforeEach(inject(function($rootScope, $pile) {
        scope = $rootScope;
        elm = angular.element('<div clickhide></div>');
        $pile(elm)(scope);
        scope.$digest();
    }));

    it('should do a click', function() {
        elm[0].click();
        //OOPS: undefined is not a function error
        expect($(elm).find(":hidden").length).toBe(1);
    });
});

When I run this test, it fails with an error of "undefined is not a function" meaning that jQuery was not loaded and Angular used jQuery Lite instead, which doesn't define hide().

I have found two ways to work around this.

  1. Don't use jQuery. Requires rewriting my directives which I don't want to do.

  2. Explicitly wrap elements in my directives to make them jQuery elements: $(element).hide(). Also requires modifications to my directives, which I would prefer not to do. If there is no alternative, I can certainly do this.

I feel like it should be possible to get Angular to automatically use jQuery inside a directive when unit testing like it does when using in a web browser. I believe the key point is this line from Angular's documentation:

To use jQuery, simply load it before DOMContentLoaded event fired.

Its not clear to me when DOMContentLoaded happens when running unit tests. My Karma config file includes jQuery as the first file, before angular:

module.exports = function(config) {
  config.set({
    basePath: '../../',
    frameworks: ['jasmine', 'ng-scenario'],
    files: [
      'app/bower_ponents/jquery/dist/jquery.min.js',
      'app/bower_ponents/angular/angular.js',
      'app/bower_ponents/angular-route/angular-route.js',
      'app/bower_ponents/angular-sanitize/angular-sanitize.js',
      'app/bower_ponents/angular-mocks/angular-mocks.js',
      ...
    ],
    exclude: [],
    reporters: ['progress'],
    port: 9876,
    runnerPort: 9100,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    captureTimeout: 60000,
    singleRun: false
  });
};

Do I need to inject jQuery into the test? Maybe this is a limitation of Karma? Anyone know?

I am having trouble writing unit tests for some of my Angular directives. Particularly, those that use jQuery inside the directive. I have contrived an minimal example below that illustrates my issue.

This silly directive binds the click event to the element. When clicked, it hides the element. According to Angular, elements passed into directives will be wrapped as jQuery elements. if jQuery is available it will use jQuery, otherwise it will use Angular's jQuery Lite. Indeed, if I use this directive in a browser with jQuery included, the directive works and will hide the clicked element.

angular.module('myApp').directive('clickhide', function() { return {
    link: function(scope, element, attrs) { 
        element.bind('click', function(e) {
            element.hide();
            scope.$digest();
        });
    }
}});

Here is the Karma unit test spec for this directive:

describe('clickhide', function() {
    var scope, elm;

    beforeEach(module('MyApp'));

    beforeEach(inject(function($rootScope, $pile) {
        scope = $rootScope;
        elm = angular.element('<div clickhide></div>');
        $pile(elm)(scope);
        scope.$digest();
    }));

    it('should do a click', function() {
        elm[0].click();
        //OOPS: undefined is not a function error
        expect($(elm).find(":hidden").length).toBe(1);
    });
});

When I run this test, it fails with an error of "undefined is not a function" meaning that jQuery was not loaded and Angular used jQuery Lite instead, which doesn't define hide().

I have found two ways to work around this.

  1. Don't use jQuery. Requires rewriting my directives which I don't want to do.

  2. Explicitly wrap elements in my directives to make them jQuery elements: $(element).hide(). Also requires modifications to my directives, which I would prefer not to do. If there is no alternative, I can certainly do this.

I feel like it should be possible to get Angular to automatically use jQuery inside a directive when unit testing like it does when using in a web browser. I believe the key point is this line from Angular's documentation:

To use jQuery, simply load it before DOMContentLoaded event fired.

Its not clear to me when DOMContentLoaded happens when running unit tests. My Karma config file includes jQuery as the first file, before angular:

module.exports = function(config) {
  config.set({
    basePath: '../../',
    frameworks: ['jasmine', 'ng-scenario'],
    files: [
      'app/bower_ponents/jquery/dist/jquery.min.js',
      'app/bower_ponents/angular/angular.js',
      'app/bower_ponents/angular-route/angular-route.js',
      'app/bower_ponents/angular-sanitize/angular-sanitize.js',
      'app/bower_ponents/angular-mocks/angular-mocks.js',
      ...
    ],
    exclude: [],
    reporters: ['progress'],
    port: 9876,
    runnerPort: 9100,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    captureTimeout: 60000,
    singleRun: false
  });
};

Do I need to inject jQuery into the test? Maybe this is a limitation of Karma? Anyone know?

Share Improve this question edited Aug 9, 2014 at 3:10 Jake asked Jul 25, 2014 at 16:37 JakeJake 1,1851 gold badge14 silver badges26 bronze badges 13
  • Could you try expect(elm.find(":hidden").length).toBe(1);? – glepretre Commented Jul 28, 2014 at 9:07
  • It doesn't even get to that line because it has an error on the line above because of the thing I described. – Jake Commented Jul 29, 2014 at 15:12
  • oh ok. How have you tried to load jQuery in your app? – glepretre Commented Jul 30, 2014 at 7:07
  • In my karma config file, I load jQuery as the first file in the "files" section and jQuery works. It loads. But for some reason angular still uses jQuery Lite as the default element wrapper. – Jake Commented Jul 31, 2014 at 15:08
  • 1 Not yet. I would like to but haven't tried too hard to figure it out. In the meantime, I use integration testing (Protractor) to cover my directives. – Jake Commented Oct 31, 2014 at 21:44
 |  Show 8 more ments

2 Answers 2

Reset to default 2

Try to override $ in beforeEach block of the test.

beforeEach(function() {
  $.fn.hide = function() {
    this.css( "display", "none" )
  };
});

I was able to resolve similar issue by loading jquery before angular. Also if $ is failing for you, You can always use angular.element('') in place of $

本文标签: javascriptTest an Angular directive that uses jQueryStack Overflow