【问题标题】:How to trigger $onInit or $onChanges implictly in unit testing Angular component controller?如何在单元测试 Angular 组件控制器中隐式触发 $onInit 或 $onChanges?
【发布时间】:2016-07-28 08:40:14
【问题描述】:

我使用 Angular 1.5.5 和 Jasmine 作为测试框架。目前我必须做这样的事情才能通过测试:

function createController(bindings) {
    return $componentController('myController', null, bindings);
}

beforeEach(inject(function (_$componentController_) {
    $componentController = _$componentController_;
}));

describe('on pages updated', function () {

    beforeEach(function () {
        controller = createController({prop1: 0, prop2: 0});
        controller.$onInit(); // you see I have to explitcitly call this $onInit function
    });

    it('should update isSelected and currentPage', function () {
        expect(controller.prop1).toBe(0);
        expect(controller.prop2).toBe(0);

        controller.prop1= 1;
        controller.prop2= 2;
        controller.$onChanges(controller); // and $onChanges here as well

        expect(controller.prop1).toBe(1);
        expect(controller.prop2).toBe(2);
    });

});

【问题讨论】:

  • 你为什么要首先隐式触发它? $onInit 是安装组件时调用的生命周期钩子。 $componentController 没有理由在单元测试中单独调用该函数。除非您将组件作为一个整体进行测试,否则您绝对应该明确地触发这些事情。

标签: angularjs unit-testing


【解决方案1】:

github 中有一个关于此的问题:https://github.com/angular/angular.js/issues/14129

基本上它按预期工作,不会自动调用$onInit$onChanges

执行 $onInit 没有意义(或低意义),我解释一下:$componentController 是对实例控制器的一种替代 $controller,但它不是创建由 controllerProvider 注册的控制器实例,而是创建通过指令注册的控制器(满足组件定义的控制器)。所以,一旦你有了控制器的实例,你就可以手动调用 $onInit,以及控制器的整个生命周期,这个想法是你正在测试一个控制器,而不是一个指令(及其关系)。

【讨论】:

    【解决方案2】:

    我不知道这是否会有所帮助,但为了测试组件,我会执行以下操作

    beforeEach(module('templates'));
    
    var element;        
    var scope;
    
    beforeEach(inject(function ($rootScope, $compile) {
        scope = $rootScope.$new();
        scope.draw = new ol.source.Vector({ wrapX: false });
        element = angular.element('<custom-element draw="draw"></custom-element>');
        element = $compile(element)(scope);
    }));
    
    var controller;
    beforeEach(inject(function ($rootScope, $componentController) {
        scope = $rootScope.$new();
        scope.draw = new ol.source.Vector({ wrapX: false });
        controller = $componentController('customElement', {draw: new ol.source.Vector({ wrapX: false })}, { $scope: scope });
    }));
    

    并且 $onInit() 和 $onChanges() 在它们应该被触发的时候被自己触发

    【讨论】:

    • 这不会给你一个实际上与你正在编译的组件没有关联的控制器吗?
    • 所以在我看来你的技巧是使用 $compile 服务来编译指令,对吧?
    • @DanPantry 为什么要这样做,您正在编译一个组件并要求该组件控制器
    • 从我在这里阅读的内容来看,您正在编译一个组件,然后请求该组件控制器的一个实例,但不一定是该组件编译时使用的控制器实例(如果这使得任何意义)。 IOW,$compile() 正在创建一个控制器,然后您正在使用 $componentController 创建 另一个 控制器。它是同一个控制器类,但与$compile 创建的类实例不同。我认为更好的方法是angular.element(...).controller(),它可以让你获得与组件关联的控制器。
    【解决方案3】:

    您需要从元素的编译版本中获取控制器的引用。如下图:

    describe('Component: Test Method', function () {
    
            beforeEach(inject(function (_$rootScope_) {
                scope = _$rootScope_.$new();
            }));
    
    
            it('should set value of selectedPackage to null after $onChanges event', inject(function ($compile) {
                // Trigger 1st $onChanges
                scope.selectedPackage = 'packageCode';
    
                var element = angular.element('<select-my-tv selected-package="selectedPackage"></select-my-tv>');
                element = $compile(element)(scope);
                scope.$digest();
    
                //Extract the Controller reference from compiled element
                var elementController = element.isolateScope().$ctrl;
    
                // Assert
                expect(elementController.selectedPackage).toBeNull();
            }));
        });
    });
    

    【讨论】:

    • 但是你为什么期望selectedPackagenull?我知道当你new-up 控制器时不会调用$onInit,但是当你使用$compile 渲染实际组件时为什么不调用它?当然,这应该会触发所有生命周期钩子。
    猜你喜欢
    • 1970-01-01
    • 2016-07-29
    • 2016-10-31
    • 1970-01-01
    • 2017-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多