我正在按照blog post中的约定编写一些单元测试,但是我无法访问指令的控制器函数。我有一个用指令和控制器的ES6类编写的指令。我正在使用controllerAs
将我的控制器类绑定到我的指令类。我试图编写单元测试的指令看起来像这样:
// Why is this file included? | Refer to : http://www.michaelbromley.co.uk/blog/350/exploring-es6-classes-in-angularjs-1-x#_section-directives
import directiveFactory from '../../../directivefactory.js';
// ##Directive Definition
class sideNav {
constructor() {
this.template =
`
<!-- SIDENAV -->
<!-- hamburger menu toggle visible when the sidenav menu is toggled shut -->
<span class="glyphicon glyphicon-menu-hamburger side-nav-hamburger dark-hamburger" set-class-when-at-top="fix-to-top" ng-click='vm.test(); vm.toggle();'></span>
<!-- wraps all sidenav menu content -->
<div ng-class='{ show: vm.open }' class="collapsible">
<!-- hamburger menu toggle visible when the sidenav menu is toggled open -->
<span class="glyphicon glyphicon-menu-hamburger side-nav-hamburger light-hamburger" ng-click='vm.test(); vm.toggle();'></span>
<!-- brand-image -->
<div class="side-nav-head" transclude-id="head"></div> <!-- component user content insertion point 1 -->
<!-- navigation links -->
<div class="side-nav-body" transclude-id="body"></div> <!-- component user content insertion point 2 -->
<!-- footer -->
<footer>
</footer>
</div><!-- end collapsible -->
<!-- END SIDENAV -->
`;
this.restrict = 'E';
this.scope = {};
this.bindToController = {
};
this.transclude = true;
this.controller = SideNavController;
this.controllerAs = 'vm';
}
// ###Optional Link Function
link (scope, elem, attrs, ctrl, transclude) {
transclude ((clone) => {
angular.forEach(clone, (cloneEl, value) => {
// If the cloned element has attributes...
if(cloneEl.attributes) {
// Get desired target ID...
var tId = cloneEl.attributes["transclude-to"].value;
// Then find target element with that ID...
var target = elem.find('[transclude-id="' + tId + '"]');
// Append the element to the target
target.append(cloneEl);
}
});
});
}
}
// ###Directive Controller
class SideNavController {
constructor($rootScope) {
this.$rootScope = $rootScope;
// Initiate the menu as closed
this.open = false;
// Upon instantiation setup necessary $rootScope listeners
this.listen();
}
// ####listen()
// *function*
// Setup directive listeners on the $rootScope
listen () {
// Receives an event from the ng-click within the directive template
// for the side-nav-item component
this.$rootScope.$on('navigation-complete', (event) => {
// Upon receiving event, toggle the menu to closed
this.toggle();
});
}
// ####toggle()
// *function*
// Toggle menu open or shut
toggle() {
this.open = !this.open;
}
// ####test()
// *function*
test() { // DEBUG
console.log('tester'); // DEBUG
console.log(this.visible); // DEBUG
console.log(this.open); // DEBUG
}
}
SideNavController.$inject = ['$rootScope'];
export default ['sideNav', directiveFactory(sideNav)];
我将此文件与其他一个指令组件一起导入,以创建如下模块:
import { default as sideNav } from './side-nav/side-nav.js';
import { default as sideNavItem } from './side-nav-item/side-nav-item.js';
let moduleName = 'sideNav';
let module = angular.module(moduleName, [])
// #### Sidebar Nav Components
.directive(...sideNav)
.directive(...sideNavItem);
export default moduleName;
在我的单元测试中,我尝试在beforeEach
中模拟控制器,但不管我是否将控制器名称用作vm
或SideNavController
(前者是控制器名称,后者是后者作为实际的类名 - 不确定哪个是我想要的那个)我仍然收到错误:Error: [ng:areq] Argument 'vm/SideNavController' is not a function, got undefined
。
这是我的单元测试:
describe('Side Nav Directive', () => {
let elem, scope, ctrl;
// Mock our side-nav directive
beforeEach(angular.mock.module('sideNav'));
beforeEach(angular.mock.inject(($rootScope, $compile, $controller) => {
// Define the directive markup to test with
elem = angular.element(
`
<div>
<!-- side-nav directive component -->
<side-nav>
<!-- content insertion point 1 -->
<div transclude-to="head">
<img src alt="test_image">
</div>
<!-- content insertion point 2 -->
<div transclude-to="body">
<a href="#">Test Link</a>
</div>
</side-nav>
</div>
`
);
scope = $rootScope.$new();
$compile(elem)(scope);
scope.$digest();
ctrl = $controller('vm', scope);
}));
it("should toggle shut when angular view navigation completes", () => {
expect(ctrl).toBeDefined(); // <----- this errors
});
});
在参考了许多教程和博客文章之后我真的很难过,并且真的可以使用一些见解!
答案 0 :(得分:0)
我实际上建议采用略有不同的测试方法。如果目标是测试SideNavController中的逻辑,我会考虑将该类移动到它自己的文件中。这样你就可以在指令和测试中导入它。以这种方式构建它可以让您更容易地访问它,因为您可以完全独立于指令本身进行测试。
通过编译标记并创建整个指令来测试它基本上将其转变为集成测试,并且管理起来有点复杂。 总的来说,我发现这可以提供更多可维护和有用的测试 - 特别是如果目标是测试控制器。
这与我的例子类似:http://www.syntaxsuccess.com/viewarticle/writing-jasmine-unit-tests-in-es6
答案 1 :(得分:0)
我实施的解决方案基于对原始问题的回应TGH。我现在将控制器类存储在一个单独的文件中,这会在指令转换功能和指令控制器功能之间产生关注点。指令控制器文件如下所示:
const ROOTSCOPE = new WeakMap();
// ###Directive Controller
class SideNavController {
constructor($rootScope) {
ROOTSCOPE.set(this, $rootScope);
// Initiate the menu as closed
this.open = false;
// Upon instantiation setup necessary $rootScope listeners
this.listen();
}
// ####listen()
// *function*
// Setup directive listeners on the $rootScope
listen () {
// Receives an event from the ng-click within the directive template
// for the side-nav-item component
ROOTSCOPE.get(this).$on('navigation-complete', (event) => {
// Upon receiving event, toggle the menu to closed
this.toggle();
});
}
// ####toggle()
// *function*
// Toggle menu open or shut
toggle() {
this.open = !this.open;
}
}
SideNavController.$inject = ['$rootScope'];
export default SideNavController;
测试文件从相应的文件中导入该类,并在beforeEach
块中模拟控制器以进行测试。控制器的变量和功能都可用:
// Import the controller to be tested
import SideNavController from './SideNavController.js';
describe('SideNavController', () => {
let $rootScope, vm;
beforeEach(angular.mock.inject((_$rootScope_, _$controller_) => {
$rootScope = _$rootScope_.$new();
vm = _$controller_(SideNavController, {} );
}));
it('should be initialize the open variable to false', () => {
expect(vm.open).toBeDefined();
expect(vm.open).toBeFalsy();
});
it('should listen for a navigation event and call the toggle function when the event is caught', () => {
// Create Jasmine spy to watch toggle function
spyOn(vm, 'toggle');
// Simulate navigation event propagation
$rootScope.$emit('navigation-complete');
expect(vm.toggle).toHaveBeenCalled();
});
});