2015-07-12 166 views
1

我正在編寫一些單元測試,遵循這個blog post中的慣例,但我無法訪問我的指令的控制器功能。我有一個爲ES指令和控制器編寫的指令。我正在使用controllerAs將我的控制器類綁定到我的指令類。該指令我想寫於容貌單元測試,像這樣:問題單元測試Karma/Jasmine的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(前者是controllerAs名稱,後者是實際的類名稱 - 不確定哪個是我想要的)我仍然收到錯誤: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

我實際上會建議一種稍微不同的測試方法。如果目標是測試SideNavController中的邏輯,我會考慮將該類移到它自己的文件中。這樣你就可以在指令和測試中導入它。以這種方式構建它可以讓您更容易地訪問它,因爲您可以完全隔離指令本身進行測試。

通過編譯標記和創建整個指令進行測試,基本上將其轉化爲集成測試,並且管理起來更復雜一些。一般而言,我發現這使得更多的可維護和有用的測試 - 特別是如果目標是測試控制器。

這將是類似於我的例子在這裏:http://www.syntaxsuccess.com/viewarticle/writing-jasmine-unit-tests-in-es6

+0

啊我明白了。是的,我之前已經閱讀過您的文章,我認爲這是我將採取的方法。我想我忘了指令控制器與指令中的關注問題分離邏輯等。感謝您的迴應。 –

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(); 
    }); 
});