64

在Vojta Jina的優秀存儲庫中,他演示了指令的測試,他定義了模塊包裝器之外的指令控制器。看到這裏: https://github.com/vojtajina/ng-directive-testing/blob/master/js/tabs.jsAngular中的單元測試指令控制器,無需將控制器全局化

是不是不好的做法和污染全球命名空間?

如果有人在另一個地方調用TabsController可能是合乎邏輯的,那會不會破壞東西?

對於所提到的指令的測試是在這裏找到:https://github.com/vojtajina/ng-directive-testing/commit/test-controller

是否有可能測試指令控制器從指示的其餘部分分開,不將其控制在一個全局命名空間?

將整個指令封裝在app.directive(...)定義中會很好。

+0

請問有什麼要點測試指令的控制器與指令本身分開?我懷疑如果控制器的代碼沒有沿着指令的其餘部分進行測試,那麼你做錯了什麼。這有點像說我有這個類的方法,但我想單獨測試它,所以我將把它放在全局範圍內。我認爲接受的答案(pkozlowski)涉及不好的練習,而詹姆斯的答案是正確的。 – Izhaki 2015-08-20 14:58:22

回答

57

優秀的問題!

所以,這是一個普遍的問題,不僅與控制器有關,而且可能與服務有關,指令可能需要執行其工作,但不一定要將此控制器/服務公開給「外部世界」。

我堅信全球數據是邪惡的,應該避免,這也適用於指令控制器以及。如果我們採取這種假設,我們可以採取幾種不同的方法來「本地」定義這些控制器。在這樣做的時候,我們需要記住,控制器應該仍然可以「容易」進入單元測試,所以我們不能簡單地將它隱藏到指令的關閉中。 IMO可能性是:

1)首先,我們可以簡單地定義在模塊級指令的控制器,前::

angular.module('ui.bootstrap.tabs', []) 
    .controller('TabsController', ['$scope', '$element', function($scope, $element) { 
    ... 
    }]) 
.directive('tabs', function() { 
    return { 
    restrict: 'EA', 
    transclude: true, 
    scope: {}, 
    controller: 'TabsController', 
    templateUrl: 'template/tabs/tabs.html', 
    replace: true 
    }; 
}) 

這是一個簡單的技術,我們正在使用https://github.com/angular-ui/bootstrap/blob/master/src/tabs/tabs.js這是基於Vojta的工作。

雖然這是一個非常簡單的技術,但應該注意的是,控制器仍然暴露給整個應用程序,這意味着其他模塊可能會覆蓋它。從這個意義上說,它使得AngularJS應用程序成爲本地控制器(所以不會污染全局窗口範圍),但它對所有AngularJS模塊也是全局的。

2)使用封閉範圍和特殊文件設置進行測試

如果我們想完全隱藏控制器函數,我們可以將代碼封裝在閉包中。這是AngularJS正在使用的技術。例如,看着NgModelController我們可以看到,它是在其自己的文件定義爲一個「全球性」的功能(並因此很容易測試訪問),但在構建時整個文件被包裹在封閉:

綜上所述:選項(2)爲「安全」的,但需要爲構建一個位的前期設置的。

+0

感謝您的回答!沒想過! – 2013-03-09 19:44:31

+0

好問題和良好的anwser。對我來說,第一種方法是完美的。 – 2013-07-30 14:14:35

+1

關於方案2;我將如何訪問testSpec中的閉包控制器定義? – Stevo 2013-10-17 07:20:57

70

我喜歡有時包括我的控制器與指令,所以我需要一種方法來測試。

首先指令

angular.module('myApp', []) 
    .directive('myDirective', function() { 
    return { 
     restrict: 'EA', 
     scope: {}, 
     controller: function ($scope) { 
     $scope.isInitialized = true 
     }, 
     template: '<div>{{isInitialized}}</div>' 
    } 
}) 

則測試:

describe("myDirective", function() { 
    var el, scope, controller; 

    beforeEach inject(function($compile, $rootScope) { 
    # Instantiate directive. 
    # gotacha: Controller and link functions will execute. 
    el = angular.element("<my-directive></my-directive>") 
    $compile(el)($rootScope.$new()) 
    $rootScope.$digest() 

    # Grab controller instance 
    controller = el.controller("myDirective") 

    # Grab scope. Depends on type of scope. 
    # See angular.element documentation. 
    scope = el.isolateScope() || el.scope() 
    }) 

    it("should do something to the scope", function() { 
    expect(scope.isInitialized).toBeDefined() 
    }) 
}) 

更多的方式來從一個實例化的指令得到的數據見angular.element documentation

請注意,實例化指令意味着控制器和所有鏈接函數已經運行,因此可能會影響您的測試。

+14

這是一個很好的答案,但是需要一個小的編輯:您需要傳入指令名稱以調用'el.controller()'。所以在上面的例子中,調用將是'el.controller(「myDirective」)'。 – fiznool 2014-07-31 09:19:26

+0

控制器沒有名稱。這只是指令定義的一部分。 'el.controller()'起作用。 – 2014-07-31 20:37:21

+4

@fiznool是正確的,至少根據官方[angular文檔](https://docs.angularjs.org/api/ng/function/angular.element#methods)。請注意,這不是指令的控制器名稱,而是指令名稱本身! – ArtoAle 2014-09-16 13:38:37

9

詹姆斯的方法適用於我。一個小的轉折是,當你有一個外部模板時,你必須在$ rootScope。$ digest()之前調用$ httpBackend.flush()以讓角度執行你的控制器。

我想這不應該是一個問題,如果你正在使用https://github.com/karma-runner/karma-ng-html2js-preprocessor

+0

使用'$ httpBackend.flush()'是關鍵。拯救了我的一天! – rkrishnan 2016-11-29 18:35:28

4

有什麼不妥做這種方式?似乎更好,因爲你避免把你的控制器放在全局名字空間中,並且能夠測試你想要的東西(即控制器)而不會不必要地編譯html。

舉例指令定義:

.directive('tabs', function() { 
    return { 
    restrict: 'EA', 
    transclude: true, 
    scope: {}, 
    controller: function($scope, $attrs) { 
     this.someExposedMethod = function() {}; 
    }, 
    templateUrl: 'template/tabs/tabs.html', 
    replace: true 
    }; 

然後在你的茉莉花測試,要求該指令您已使用 「姓名+指令」(如 「tabsDirective」。):

var tabsDirective = $injector.get('tabsDirective')[0]; 
// instantiate and override locals with mocked test data 
var tabsDirectiveController = $injector.instantiate(tabsDirective.controller, { 
    $scope: {...} 
    $attrs: {...} 
}); 

現在你可以測試控制器的方法:

expect(typeof tabsDirectiveController.someExposedMethod).toBe('function'); 
+1

@ pkozlowski.opensource我錯過了這種方法? – jbmilgrom 2016-08-05 11:42:49

+0

我試過這種方法訪問控制器,它工作正常。但是我被這種方式卡住瞭如何傳遞指令屬性。我有一個像這樣的指令 - >'' 我想訪問ref-object -uri參數在單元測試中。 – Anita 2017-10-06 12:12:00

+0

我的答案只是測試控制器功能。您的示例「html」對應於引導下的指令,需要獲得'$ compile'來測試屬性參數的傳遞情況 – jbmilgrom 2017-10-06 12:28:49

0

使用IIFE,這是一種常見的技術,以避免全局命名空間衝突&它還可以節省棘手的內聯體操,並在你的範圍內提供自由。

(function(){ 

    angular.module('app').directive('myDirective', function(){ 
    return { 
     ............. 
     controller : MyDirectiveController, 
     ............. 
    } 
    }); 

    MyDirectiveController.$inject = ['$scope']; 

    function MyDirectiveController ($scope) { 

    } 

})();