[英]Unit Testing Angular 1.5 component that requires ngModel
To test angular 1.5 components, the docs recommend you use ngMock's $componentController instead of using $compile if you don't need to test any of the DOM. 要测试angular 1.5组件,文档建议您使用ngMock的$ componentController而不是使用$ compile,如果您不需要测试任何DOM。
However, my component uses ngModel which I need to pass into the locals
for $componentController, but there is no way to programmatically get the ngModelController; 但是,我的组件使用了我需要传递给$ componentController的locals
的ngModel,但没有办法以编程方式获取ngModelController; the only way to test it is to actually $compile an element with it on it, as this issue is still open: https://github.com/angular/angular.js/issues/7720 . 测试它的唯一方法是实际$在其上编译一个元素,因为这个问题仍然存在: https : //github.com/angular/angular.js/issues/7720 。
Is there any way to test my components controller without resorting to $compiling it? 有没有办法测试我的组件控制器而不采用$编译它? I also don't want to have to mock the ngModelController myself as its behavior is somewhat extensive and if my tests rely on a fake one rather than the real thing there is a chance newer versions of Angular could break it (though that probably isn't an issue given Angular 1 is being phased out). 我也不想自己模仿ngModelController,因为它的行为有点广泛,如果我的测试依赖于假的而不是真实的东西,那么新版本的Angular可能会破坏它(尽管可能不是'给定Angular 1的问题正在逐步淘汰)。
tl;dr: Solution is in the third code block. tl; dr:解决方案在第三个代码块中。
but there is no way to programmatically get the ngModelController 但是没有办法以编程方式获取ngModelController
Not with that attitude. 不是那种态度。 ;) ;)
You can get it programmatically, just a little roundabout. 你可以通过编程方式获得它,只是一个小环形。 The method of doing so is in the code for ngMock
's $componentController
service (paraphrased here); 这样做的方法是在ngMock
的$componentController
服务的代码中 (这里解释); use $injector.get('ngModelDirective')
to look it up, and the controller function will be attached to it as the controller
property: 使用$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);
};
}];
Though you need to supply the ngModelController locals for $element
and $attrs
when you instantiate it. 虽然您需要在实例化时为$element
和$attrs
提供ngModelController本地。 The test spec for ngModel
demonstrates exactly how to do this in its beforeEach
call : ngModel
的测试规范演示了如何在beforeEach
调用中执行此beforeEach
:
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;
}));
So, adapting that to what we need, we get a spec like this: 因此,根据我们的需要进行调整,我们得到如下规格:
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');
});
});
ADDENDUM: You can also simplify it further by instead injecting ngModelDirective
in the beforeEach
and setting a var in the describe
block to contain the controller function, like you do with services like $controller
. 附录:您还可以通过在beforeEach
注入ngModelDirective
并在describe
块中设置var以包含控制器函数来进一步简化它,就像使用$controller
类的服务一样。
describe('...', function () {
var ngModelController;
beforeEach(inject(function(_ngModelDirective_) {
ngModelController = _ngModelDirective_[0].controller;
}));
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.