Angularjs 基於karma和jasmine的單元測試
目錄:
1. 單元測試的配置
2. 實例文件目錄解釋
3. 測試controller
3.1 測試controller中變量值是否正確
3.2 模擬http請求返回值,測試$http服務相關
4. 從文件中讀取json,來模擬 http請求返回數據
5. 測試返回promise的service
已經有很多教程提到了angularjs項目的單元測試,但大都不是很全,如一些入門的文章,介紹了測試http service 卻沒有介紹如何從文件中讀取測試數據來仿真。一些介紹如何從文件中讀取仿真數據的文章對入門則太深入。所以寫了這個在工作中經常會遇到的情況的教程。希望有點用:)
1. 單元測試的配置
- 安裝 angular
npm install angular --save - 安裝 karma
npm install -g karma --save-dev - 安裝 Jasmine
npm install karma-jasmine jasmine-core --save-dev - 安裝 ngMock
npm install angular-mocks --save-dev - 安裝 jasmine-jquery
bower install jasmine-jquery --save - 安裝 karma-read-json
bower install karma-read-json - 下載實例
https://github.com/wuhaibo/angularUnitTest
2. 實例文件目錄解釋
3. 測試controller
首先看看我們的controller的代碼
1 'use strict'; 2 /* Controllers */ 3 /* module */ 4 var unitTestApp = angular.module('unitTestApp', []); 5 6 /* Controllers */ 7 unitTestApp.controller('unitTestCtrl', function($scope,$http) { 8 9 //set name 10 $scope.name = "william wood"; 11 12 //通過http請求得到user 13 $scope.GetUser = function(){ 14 $http.get('/auth.py').then(function(response) { 15 $scope.user = response.data; 16 }); 17 }; 18 });
這個controller很簡單, 有兩個元素
- 在scope里聲明了一個變量name, 並賦值 williamwood
- 定義了一個函數GetUser, 這個函數發送一個http get請求,來給scope.user 賦值
我們先測試 1 再測試 2.
3.1 測試controller中變量值是否正確
測試的代碼在 /test/unit/controllersSpec.js, 測試代碼簡單說明如下
1 'use strict'; 2 3 //測試類型描述,這里表示測試unitTestApp的controllers 4 describe('unitTestApp controllers', function() { 5 6 //測試類型描述,這里表示測試unitTestCtrl這個controller 7 describe('unitTestCtrl', function(){ 8 9 //beforeEach 表示在運行所有測試前的准備工作。 10 //這里生成unitTestApp 的module 11 beforeEach(module('unitTestApp')); 12 13 //定義在測試中會用到的object,以便在整個測試環境中使用 14 var scope,ctrl; 15 16 //inject利用angular的依賴注入,將需要的模塊,服務插入作用域 17 beforeEach(inject(function ($controller, $rootScope) { 18 //模擬生成scope, $rootScope是angular中的頂級scope,angular中每個controller中的 19 //scope都是rootScope new出來的 20 scope = $rootScope.$new(); 21 //模擬生成controller 並把先前生成的scope傳入以方便測試 22 ctrl = $controller('unitTestCtrl', {$scope: scope}); 23 })); 24 25 //測試從這里開始 26 // it 里'should create name william wood in unitTestCtrl' 說明測試的項目 27 it('should create name william wood in unitTestCtrl', 28 inject(function() { 29 //測試期望 scope.name 的值為 william wood 30 expect(scope.name).toEqual('william wood'); 31 })); 32 33 //測試GetUser函數,詳細將在下面介紹 34 it('GetUser should fetch users', inject(function($injector){ 35 .... 36 })); 37 }); 38 }); 39
在jasmine中用describe來描述testcase類別(如是測試哪個controller,哪個modular。。。), beforeEach 用來做測試前的准備工作,inject利用angular的依賴注入,將需要的模塊,服務插入作用域。真正的測試代碼在it函數里,這個函數的第一個參數為testcase描述,第二個函數為測試邏輯.
測試配置(可以跳過這一步)
測試可以用karma init命令配置, 這個命令會生成karma.conf.js 文件來作為測試配置文件。由於實例文件夾中已經有了這個文件就可以跳過這一步。以后可以使用實例文件結構作為其他項目的基礎模板。
運行測試
1. Windows commandline 進入到 karma.conf.js 所在目錄。
2. 運行指令 karma start, 這時會彈出瀏覽器窗口,不用管,它們被啟動來執行測試,就讓他們在后台呆着就可以。 karma會自動監視文件改動自動執行測試。測試成功如下圖所示,這里因為在測試文件中有兩個測試用例,所以可以看到 Executed 1 of 2 … 字樣(為了測試方便,firefox測試平台被注釋掉,所有測試將只在chrome上運行,如果要使用firefox來運行測試只需要將karma.conf.js 里的 browsers : ['Chrome'/*, 'Firefox'*/] 改為 browsers : ['Chrome', 'Firefox']即可)
3. 測試失敗的情況
修改expect(scope.name).toEqual('william wood')為
expect(scope.name).toEqual('william wood is me');
保存后切換到命令行窗口,發現測試自動運行了,並有錯誤報告。
Ok 到此為止我們已經可以測試一個controller了。下面我們介紹如何模擬http請求的返回值測試$http服務相關的邏輯。
3.2 模擬http請求返回值,測試$http服務相關
記得我們在controller中有一個GetUser函數
1 //通過http請求得到user 2 $scope.GetUser = function(){ 3 $http.get('/auth.py').then(function(response) { 4 $scope.user = response.data; 5 });
這個函數通過http get請求得到user的值。
在單元測試里我們並不真的希望發送一個http get請求來運行測試,因為那樣會使測試復雜化,網絡相關的各種問題都會導致測試失敗,而且angular http服務是異步的,而我們希望測試是同步的。那么怎么做呢?
先來看測試的代碼,仍然在 /test/unit/controllersSpec.js
//模擬http get的返回值, 插入injector服務,讓我們能夠在測試代碼中使用依賴注入來獲得需要的服務 it('GetUser should fetch users', inject(function($injector){ // $httpBackend 是由angular mock提供的一個模擬http請求返回服務 // 可以用它來模擬http請求的返回值 // 這里通過$injector來獲取它的實例 var $httpBackend = $injector.get('$httpBackend'); // $httpBackend 在Get方法,對 '/auth.py' 的url將會返回 一個jason對象 // {customerId: '1',name:'benwei'} $httpBackend.when('GET', '/auth.py').respond({customerId: '1',name:'benwei'}); //以上為測試前的准備工作, 也可以把這部分代碼放在beforeEach里, //但要注意: beforeEach里的設置將影響所有在它作用域的測試用例。 //運行GetUser函數 scope.GetUser(); //把http的異步轉為同步,要求$httpBackend立刻返回數據 $httpBackend.flush(); // 查看scope.user的值是否正確 expect(scope.user).toEqual({customerId: '1',name:'benwei'}); }));
4. 從文件中讀取json,來模擬 http請求返回數據
有些時候我們需要返回比較大的json數據, 這時json數據像上面這樣寫在測試代碼里就不大現實。比較可行的方案是把json數據保存在json文件中,並從文件中讀取數據。這時我們就需要Karma-Read-JSON的幫助。
我們已經在單元測試的配置中安裝了這個插件,並在 /test/karma.conf.js 中做了設置,這里對設置進行簡單的說明。(可以跳過閱讀這一步,只要記得將模擬使用的 json文件放在 test/mock/ 文件夾中,並且文件后綴為.json)
1.在測試中引入karma-read-json框架
files : [ … //test framework 'app/bower_components/karma-read-json/karma-read-json.js', … ],
2. 向karma指定在測試中會用到的模擬數據文件格式,
files : [ … // fixtures {pattern: 'test/mock/*.json', included: false}, … ],
注意這里的根目錄是在karma.conf.js文件中設置的,如下
basePath : '../', //設置 karma.conf.js所在目錄/../ 為根目錄
在本實例中模擬數據需要的 json文件應該放在test/mock 文件夾中
當設置進行完后,再來看我們的測試代碼
it('GetUser should fetch users mock response from file', inject(function($injector){ //從文件中讀取模擬返回數據 var valid_respond = readJSON('test/mock/data.json'); // 這里通過$injector來獲取它的實例獲取 httpBackend服務的實例 var $httpBackend = $injector.get('$httpBackend'); // $httpBackend 在Get方法,對 '/auth.py' 的url將會返回 // 一個從test/mock/data.json讀取的json對象 $httpBackend.when('GET', '/auth.py').respond(valid_respond); // $httpBackend 在Get方法,對 '/auth.py' 的url將會返回 一個jason對象 // {customerId: '1',name:'benwei'} $httpBackend.when('GET', '/auth.py').respond({customerId: '1',name:'benwei'}); //運行GetUser函數 scope.GetUser(); //把http的異步轉為同步,要求$httpBackend立刻返回數據 $httpBackend.flush(); // 查看scope.user的值是否正確 expect(scope.user.length).toBe(2); }));
5. 測試返回promise的service
先來看看service代碼,代碼可在app\js\services.js 中找到
'use strict'; /* Services */ unitTestApp.factory('GetUserNumberService', function($http,$q) { var deferred = $q.defer(); //http 服務請求 $http({method: 'GET', url: '/auth.py'}).then( function(response){ deferred.resolve(response.data.length); }, function (response) { deferred.reject(response); } ); //返回http 服務請求的promise return deferred.promise; } );
我們創建了一個叫 GetUserNumberService 的服務,這個服務通過發送http請求獲得返回數據的長度。這個服務的測試代碼如下,代碼可在test\unit\servicesSpec.js 中找到
'use strict'; /* jasmine specs for services go here */ describe('serviceTest', function() { describe('Test GetUserNumberService', function() { //mock module beforeEach(module('unitTestApp')); it('GetUserNumberService should return 2', inject(function($injector) { //模擬返回數據 var valid_respond = '[{"customerId": "1","name": "benwei"},{"customerId": "2","name": "william"}]'; var $httpBackend = $injector.get('$httpBackend'); $httpBackend.whenGET('/auth.py').respond(valid_respond); // 通過injector得到service,就像在前面的例子中得到$httpBackend一樣 var getUserNumberService = $injector.get('GetUserNumberService'); var promise = getUserNumberService; var userNum; promise.then(function(data){ userNum = data; }); //強迫httpBackend返回數據 $httpBackend.flush(); //通過injector得到$rootScope var $rootScope = $injector.get('$rootScope'); //強迫傳遞到當前作用域 $rootScope.$apply(); //測試判斷userNum是否為2 expect(userNum).toEqual(2); })); }); });
有一個值得注意的地方, 為了將變化傳遞到當前作用域,所以要使用 $rootScope.$apply();