目錄:jquery
1. 單元測試的配置git
2. 實例文件目錄解釋angularjs
3. 測試controllergithub
3.1 測試controller中變量值是否正確chrome
3.2 模擬http請求返回值,測試$http服務相關npm
4. 從文件中讀取json,來模擬 http請求返回數據json
5. 測試返回promise的servicepromise
已經有不少教程提到了angularjs項目的單元測試,但大都不是很全,如一些入門的文章,介紹了測試http service 卻沒有介紹如何從文件中讀取測試數據來仿真。一些介紹如何從文件中讀取仿真數據的文章對入門則太深刻。因此寫了這個在工做中常常會遇到的狀況的教程。但願有點用:)瀏覽器
首先看看咱們的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很簡單, 有兩個元素
咱們先測試 1 再測試 2.
測試的代碼在 /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服務相關的邏輯。
記得咱們在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'}); }));
有些時候咱們須要返回比較大的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); }));
先來看看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();