AngularJS是繼jQuery之後發生在JavaScript上最好的東西。這也是JavaScript開發一直以來想要的方式。Angular主要的優點之一就是它的依賴注入(Dependency Injection),它非常利於代碼的單元測試。但有點小怪異的是,我在無論如何都沒能找到一個介紹如何做單元測試的教程。
當然有很多不錯的推薦:使用Jasmine測試架構和Karma測試執行器(Test Runner);但是並沒有一篇完整的從無到有指導如何測試的教程。所以我寫了這篇文章。我在網上找了很多資源才知道如何去做,而你現在不需要去做這些(如果一開始就看到這篇文章的話)。
請告訴我你看到的任何錯誤,直到我能說這是基於Karma和Jasmine測試Angular應用的最佳實務。
介紹
這篇文章會引導你安裝使用Karma和Jasmine做自動化測試所需要的所有工具。我不在乎你實在使用TDD(測試驅動開發)還是TAD(測試輔助開發),在這篇文章中,我假設你已經有一個檔案需要測試。
安裝Karma
如果你沒有安裝 Node.js,那麼請自行下載和安裝。安裝之後,開啟終端或命令列輸入一下命令:
檔案結構
檔案結構是跟我們的議題關聯不大,但是在接下來的測試中,我使用的檔案結構如下:
Application| angular.js| angular-resource.js| Home | home.js| Tests | Home | home.tests.js | karma.config.js (will be created in the next step) | angular-mocks.js
*我並不主張這種文檔結構,我展示它只是為了測試舉例。
配置Karma
切換到你想要放置設定檔的目錄,然後在終端中輸入下面的命令來建立設定檔:
karma init karma.config.js
你會被詢問一些問題,包括你想使用那個測試架構,你是否需要自動監測檔案,包含哪些測試和被測試檔案等。在我們的教程中,我們保留‘Jasmine'作為我們預設的架構,開啟檔案自動監測,並包含下面的檔案:
../*.js../**.*.jsangular-mocks.js**/*.tests.js
這些都是相對路徑,包含了1)父目錄下的所有.js檔案,2)父目錄下的所有子目錄下的所有.js檔案,3)目前的目錄下的angular-mock.js,4)以及目前的目錄(包含子目錄)下所有的.tests.js檔案(我喜歡以這樣的方式來區分測試檔案和其他的檔案)。
不管你選擇了什麼檔案,請確保你引入了 angular.js,angular-mock.js,以及其他你需要使用的檔案。
啟動Karma
現在已經可以啟動Karma了,依然在終端中輸入:
karma start karma.config.js
這個命令會在你的電腦上啟動你在設定檔中列出的瀏覽器。這些瀏覽器都會以socket的方式串連到Karma的執行個體上,你會看到一組活動的瀏覽器並被告知她們是否在執行測試。我但願Karma已經告訴你在每個瀏覽器上的最終測試結果總結(比如16個中的15個通過,1個失敗),遺憾的是你只能通過終端視窗看到這些資訊。
Karma的一個很突出的特色是你可以在網路中使用任何裝置串連並測試你的代碼。試一下將你的手機瀏覽器指向Karma服務,你可以在電腦上任何一個啟動並執行瀏覽器上找到這個測試的URL地址。它應該類似於:http://localhost:9876/?id=5359192。你可以將你的手機,虛擬機器,或其他任何裝置的瀏覽器指向 [你在網路上的IP地址]:9876/?id=5359192. 因為Karma是在運行一個 Node.js 執行個體,你的測試機器就像一個web伺服器一樣,會將測試發送到任何指向它的瀏覽器。
基本的測試
我們假設你已經有一個檔案需要測試。我們要使用到的 home.js 檔案如下:
home.js
'use strict'; var app = angular.module('Application', ['ngResource']); app.factory('UserFactory', function($resource){ return $resource('Users/users.json')}); app.controller('MainCtrl', function($scope, UserFactory) { $scope.text = 'Hello World!'; $scope.users = UserFactory.get();});
我們可以在 home.test.js 檔案中建立我們的測試案例。我們從簡單的那個測試開始:$scope.text 應該等於 ‘Hello World!'。 為了完成這個測試,我們需要類比我們的 Application 模組以及 $scope 變數。我們會在Jasmine的 beforeEach 方法中做這個工作,這樣的話我們在每個測試案例開始時可以有一個全新的(乾淨的)controler和scope對象。
home.tests.js
'use strict'; describe('MainCtrl', function(){ var scope;//我們會在測試中使用這個scope //類比我們的Application模組並注入我們自己的依賴 beforeEach(angular.mock.module('Application')); //類比Controller,並且包含 $rootScope 和 $controller beforeEach(angular.mock.inject(function($rootScope, $controller){ //建立一個空的 scope scope = $rootScope.$new(); //聲明 Controller並且注入已建立的空的 scope $controller('MainCtrl', {$scope: scope}); }); // 測試從這裡開始});
從代碼中你可以看到我們注入了我們自己的 scope,因此我們可以在它的外部驗證它的資訊。同時,別忘了類比模組本身(第7行代碼)!我們現在已經為測試做好了準備:
home.tests.js
// 測試從這裡開始it('should have variable text = "Hello World!"', function(){ expect(scope.text).toBe('Hello World!);});
如果你運行這個測試,它可以在任何指向Karma的瀏覽器中執行,並且測試通過。
發送$resource請求
現在我們已經準備好測試 $resource 請求。要完成這個請求,我們需要使用到 $httpBackend, 它一個類比版本的Angular $http。我們會建立另一個叫做 $httpBackend 的變數,在第二個 beforEach塊中,注入 _$httpBackend_ 並將新建立的變數指向 _$httpBackend_。接下來我們會告訴 $httpBackend 如何對請求做出響應。
$httpBackend = _$httpBackend_; $httpBackend.when('GET', 'Users/users.json').respond([{id: 1, name: 'Bob'}, {id:2, name: 'Jane'}]);
我們的測試: home.tests.js
it('should fetch list of users', function(){ $httpBackend.flush(); expect(scope.users.length).toBe(2); expect(scope.users[0].name).toBe('Bob'); });
都放到一起
home.tests.js
'use strict'; describe('MainCtrl', function(){ var scope, $httpBackend;//we'll use these in our tests //mock Application to allow us to inject our own dependencies beforeEach(angular.mock.module('Application')); //mock the controller for the same reason and include $rootScope and $controller beforeEach(angular.mock.inject(function($rootScope, $controller, _$httpBackend_){ $httpBackend = _$httpBackend_; $httpBackend.when('GET', 'Users/users.json').respond([{id: 1, name: 'Bob'}, {id:2, name: 'Jane'}]); //create an empty scope scope = $rootScope.$new(); //declare the controller and inject our empty scope $controller('MainCtrl', {$scope: scope}); }); // tests start here it('should have variable text = "Hello World!"', function(){ expect(scope.text).toBe('Hello World!'); }); it('should fetch list of users', function(){ $httpBackend.flush(); expect(scope.users.length).toBe(2); expect(scope.users[0].name).toBe('Bob'); });});
技巧
Karma會運行所有檔案中的所有測試案例,如果你只想運行所有測試的一個子集,修改 describe 或 it 為 ddescribe 或 iit 來運行個別的一些測試。如果有些測試你不想運行他們,那麼修改 describe 或 it 為 xdescribe 或 xit 來忽略這些代碼。
你也可以在html檔案的頁面上運行你的測試。舉例的代碼如下:
home.runner.html
<!DOCTYPE html><html><head> <title>Partner Settings Test Suite</title> <!-- include your script files (notice that the jasmine source files have been added to the project) --> <script type="text/javascript" src="../jasmine/jasmine-1.3.1/jasmine.js"></script> <script type="text/javascript" src="../jasmine/jasmine-1.3.1/jasmine-html.js"></script> <script type="text/javascript" src="../angular-mocks.js"></script> <script type="text/javascript" src="home.tests.js"></script> <link rel="stylesheet" href="../jasmine/jasmine-1.3.1/jasmine.css"/></head><body> <!-- use Jasmine to run and display test results --> <script type="text/javascript"> var jasmineEnv = jasmine.getEnv(); jasmineEnv.addReporter(new jasmine.HtmlReporter()); jasmineEnv.execute(); </script></body></html>