AngularJS测试:带绑定的控制器初始化时条件判断异常问题
解决AngularJS控制器绑定属性未就绪时执行逻辑的问题
这个问题的核心原因很清晰:当你通过$controller创建控制器实例时,控制器的构造函数会先执行,之后Angular才会把你传入的绑定属性(第三个参数里的内容)赋值到控制器实例上。所以在构造函数里检查ctrl.contract的时候,它还没被赋值,自然就进入else分支了。
下面给你两种可靠的解决方案,根据你的AngularJS版本选择:
方案1:使用$onInit生命周期钩子(推荐,AngularJS 1.5+)
从AngularJS 1.5开始,引入了组件式开发的生命周期钩子,其中$onInit会在所有绑定属性都完成赋值之后自动执行,完美匹配你的需求。
修改你的控制器代码,把初始化逻辑移到$onInit里:
app.controller('MyCtrl', function() { let ctrl = this; // 把初始化逻辑放到$onInit钩子中 ctrl.$onInit = function() { if (ctrl.contract) { ctrl.contract.typeId = ctrl.contract.type.id; } else { ctrl.contract = { name: 'test' }; } // 其他需要在绑定完成后执行的逻辑 }; });
然后在测试代码里,需要手动调用$onInit(因为$controller不会自动触发这个钩子,只有Angular加载组件时才会自动调用):
describe('MyCtrl', () => { let ctrl; beforeEach(angular.mock.module('mymodule')); beforeEach(angular.mock.module('ui.router')); describe('Update contract', () => { beforeEach(inject((_$controller_) => { ctrl = _$controller_('MyCtrl', {}, { contract: { type: { id: 2 } } }); // 手动调用初始化钩子,模拟组件加载时的生命周期 ctrl.$onInit(); })); it('should set the contract.typeId to be the id of the type of the contract that was sent in', () => { expect(ctrl.contract.typeId).toBe(ctrl.contract.type.id); }); }); });
方案2:使用$watch监听绑定属性(兼容AngularJS 1.5以下)
如果你的项目还在使用低于1.5的AngularJS版本,可以通过$watch监听ctrl.contract的变化,当它被赋值时再执行逻辑:
app.controller('MyCtrl', ['$scope', function($scope) { let ctrl = this; // 深度监听contract的变化,确保对象赋值时能触发 $scope.$watch(() => ctrl.contract, (newContract) => { if (newContract) { ctrl.contract.typeId = newContract.type.id; } else { ctrl.contract = { name: 'test' }; } }, true); }]);
这种方式不需要修改测试代码,因为$watch会在contract被赋值到控制器实例后触发。不过相比$onInit,它会带来额外的监听开销,所以优先推荐方案1。
内容的提问来源于stack exchange,提问作者Anton Antonsen
相关产品推荐
相关产品推荐

