【问题标题】:Unit Testing Angular 1.5 component that requires ngModel需要 ngModel 的单元测试 Angular 1.5 组件
【发布时间】:2017-08-17 18:24:59
【问题描述】:

要测试 Angular 1.5 组件,如果您不需要测试任何 DOM,文档建议您使用 ngMock 的 $componentController 而不是 $compile。

但是,我的组件使用了 ngModel,我需要将其传递给 $componentController 的locals,但是无法以编程方式获取 ngModelController;测试它的唯一方法是实际 $compile 一个带有它的元素,因为这个问题仍然存在:https://github.com/angular/angular.js/issues/7720。

有什么方法可以测试我的组件控制器而不使用 $ 编译它?我也不想自己模拟 ngModelController,因为它的行为有点广泛,如果我的测试依赖于假的而不是真实的,那么新版本的 Angular 可能会破坏它(尽管这可能不是鉴于 Angular 1 正在逐步淘汰,这是一个问题)。

【问题讨论】:

    标签: javascript angularjs unit-testing


    【解决方案1】:

    tl;dr:解决方案在第三个代码块中。

    但没有办法以编程方式获取 ngModelController

    不是那种态度。 ;)

    您可以通过编程方式获取它,只需一个小环岛。这样做的方法是in the code for ngMock's $componentController service(此处转述);使用$injector.get('ngModelDirective') 查找它,控制器功能将作为controller 属性附加到它:

    this.$get = ['$controller','$injector', '$rootScope', function($controller, $injector, $rootScope) {
        return function $componentController(componentName, locals, bindings, ident) {
            // get all directives associated to the component name
            var directives = $injector.get(componentName + 'Directive');
            // look for those directives that are components
            var candidateDirectives = directives.filter(function(directiveInfo) {
                // ...
            });
    
            // ...
    
            // get the info of the component
            var directiveInfo = candidateDirectives[0];
            // create a scope if needed
            locals = locals || {};
            locals.$scope = locals.$scope || $rootScope.$new(true);
            return $controller(directiveInfo.controller, locals, bindings, ident || directiveInfo.controllerAs);
        };
    }];
    

    虽然您需要在实例化它时为 $element 和 $attrs 提供 ngModelController 本地变量。 The test spec for ngModel demonstrates exactly how to do this in its beforeEach call:

    beforeEach(inject(function($rootScope, $controller) {
        var attrs = {name: 'testAlias', ngModel: 'value'};
    
    
        parentFormCtrl = {
            $$setPending: jasmine.createSpy('$$setPending'),
            $setValidity: jasmine.createSpy('$setValidity'),
            $setDirty: jasmine.createSpy('$setDirty'),
            $$clearControlValidity: noop
        };
    
    
        element = jqLite('<form><input></form>');
    
    
        scope = $rootScope;
        ngModelAccessor = jasmine.createSpy('ngModel accessor');
        ctrl = $controller(NgModelController, {
            $scope: scope,
            $element: element.find('input'),
            $attrs: attrs
        });
    
    
        //Assign the mocked parentFormCtrl to the model controller
        ctrl.$$parentForm = parentFormCtrl;
    }));
    

    因此,根据我们的需要调整它,我们得到这样的规范:

    describe('Unit: myComponent', function () {
        var $componentController,
            $controller,
            $injector,
            $rootScope;
    
        beforeEach(inject(function (_$componentController_, _$controller_, _$injector_, _$rootScope_) {
            $componentController = _$componentController_;
            $controller = _$controller_;
            $injector = _$injector_;
            $rootScope = _$rootScope_;
        }));
    
        it('should update its ngModel value accordingly', function () {
            var ngModelController,
                locals
                ngModelInstance,
                $ctrl;
    
            locals = {
                $scope: $rootScope.$new(),
                //think this could be any element, honestly, but matching the component looks better
                $element: angular.element('<my-component></my-component>'),
                //the value of $attrs.ngModel is exactly what you'd put for ng-model in a template
                $attrs: { ngModel: 'value' }
            };
            locals.$scope.value = null; //this is what'd get passed to ng-model in templates
            ngModelController = $injector.get('ngModelDirective')[0].controller;
    
            ngModelInstance = $controller(ngModelController, locals);
    
            $ctrl = $componentController('myComponent', null, { ngModel: ngModelInstance });
    
            $ctrl.doAThingToUpdateTheModel();
            scope.$digest();
    
            //Check against both the scope value and the $modelValue, use toBe and toEqual as needed.
            expect(ngModelInstance.$modelValue).toBe('some expected value goes here');
            expect(locals.$scope.value).toBe('some expected value goes here');
        });
    });
    

    附录:您还可以通过在 beforeEach 中注入 ngModelDirective 并在 describe 块中设置一个 var 来包含控制器功能来进一步简化它,就像使用 $controller 之类的服务一样。

    describe('...', function () {
        var ngModelController;
    
        beforeEach(inject(function(_ngModelDirective_) {
            ngModelController = _ngModelDirective_[0].controller;
        }));
    });
    

    【讨论】:

      猜你喜欢
      • 2016-10-31
      • 1970-01-01
      • 2017-05-05
      • 2016-10-07
      • 1970-01-01
      • 2017-09-15
      • 1970-01-01
      • 2017-08-28
      • 1970-01-01
      相关资源
      最近更新 更多