核心要點
angular-mocks
模塊簡化了,該模塊為一組常用的 AngularJS 服務提供了模擬。 $provide
實現模擬服務來完成。後者方法更可取,可以避免調用服務的實際方法實現。 $get
方法。如果測試文件中不需要 $get
函數中定義的功能,則可以將其賦值為空函數。 $window
或使用全局對象創建值或常量並根據需要注入它們來實現模擬。 AngularJS 的設計理念中就包含了測試。框架的源代碼經過了非常充分的測試,並且使用該框架編寫的任何代碼也都是可測試的。內置的依賴注入機制使得用 AngularJS 編寫的每個組件都可進行測試。 AngularJS 應用程序中的代碼可以使用任何現有的 JavaScript 測試框架進行單元測試。最常用於測試 AngularJS 代碼的框架是 Jasmine。本文中的所有示例代碼片段都是使用 Jasmine 編寫的。如果您在 Angular 項目中使用任何其他測試框架,您仍然可以應用本文中討論的思想。
本文假設您已經具備單元測試和測試 AngularJS 代碼的經驗。您不必是測試專家。如果您對測試有基本的了解,並且可以為 AngularJS 應用程序編寫一些簡單的測試用例,那麼您可以繼續閱讀本文。
模擬在單元測試中的作用
每個單元測試的任務都是隔離地測試一段代碼的功能。隔離被測系統有時可能具有挑戰性,因為依賴項可能來自不同的來源,我們需要充分理解要模擬的對象的職責。
在 JavaScript 等非靜態類型語言中,模擬很困難,因為不容易理解要模擬的對象的結構。同時,它也提供了靈活性,即僅模擬被測系統當前正在使用的對象的某一部分,而忽略其餘部分。
AngularJS 測試中的模擬
由於 AngularJS 的主要目標之一是可測試性,核心團隊為此付出了額外的努力,使測試更容易,並在 angular-mocks
模塊中為我們提供了一組模擬。此模塊包含圍繞一組 AngularJS 服務(例如 $http
、$timeout
、$animate
等)的模擬,這些服務廣泛用於任何 AngularJS 應用程序中。此模塊減少了開發人員編寫測試所需的大量時間。
在為真實的業務應用程序編寫測試時,這些模擬非常有幫助。同時,它們不足以測試整個應用程序。我們需要模擬框架中但未被模擬的任何依賴項——來自第三方插件的依賴項、全局對像或在應用程序中創建的依賴項。本文將介紹一些關於模擬 AngularJS 依賴項的技巧。
服務是 AngularJS 應用程序中最常見的依賴項類型。您可能已經知道,服務在 AngularJS 中是一個重載的術語。它可能指服務、工廠、值、常量或提供程序。我們將在下一節討論提供程序。服務可以通過以下方式之一進行模擬:
$provide
實現模擬服務。 我不喜歡第一種方法,因為它可能導致調用服務的實際方法實現。我們將使用第二種方法來模擬以下服務:
angular.module('sampleServices', []) .service('util', function() { this.isNumber = function(num) { return !isNaN(num); }; this.isDate = function(date) { return (date instanceof Date); }; });
以下代碼片段創建了上述服務的模擬:
module(function($provide) { $provide.service('util', function() { this.isNumber = jasmine.createSpy('isNumber').andCallFake(function(num) { // 模拟实现 }); this.isDate = jasmine.createSpy('isDate').andCallFake(function(num) { // 模拟实现 }); }); }); // 获取模拟服务的引用 var mockUtilSvc; inject(function(util) { mockUtilSvc = util; });
儘管上面的示例使用 Jasmine 創建間諜,但您可以使用 Sinon.js 替換它,實現等效的功能。
最好在加載測試所需的所有模塊後創建所有模擬。否則,如果在一個已加載的模塊中定義了一個服務,則實際實現會覆蓋模擬實現。
常量、工廠和值可以使用 $provide.constant
、$provide.factory
和 $provide.value
分別進行模擬。
模擬提供程序類似於模擬服務。編寫提供程序時必須遵循的所有規則也必須在模擬它們時遵循。考慮以下提供程序:
angular.module('mockingProviders',[]) .provider('sample', function() { var registeredVals = []; this.register = function(val) { registeredVals.push(val); }; this.$get = function() { function getRegisteredVals() { return registeredVals; } return { getRegisteredVals: getRegisteredVals }; }; });
以下代碼片段為上述提供程序創建了一個模擬:
module(function($provide) { $provide.provider('sample', function() { this.register = jasmine.createSpy('register'); this.$get = function() { var getRegisteredVals = jasmine.createSpy('getRegisteredVals'); return { getRegisteredVals: getRegisteredVals }; }; }); }); // 获取提供程序的引用 var sampleProviderObj; module(function(sampleProvider) { sampleProviderObj = sampleProvider; });
獲取提供程序和其他單例的引用的區別在於,提供程序在此時不會在 inject()
塊中可用,因為提供程序此時已轉換為工廠。我們可以使用 module()
塊獲取它們的對象。
在定義提供程序的情況下,測試中也必須實現 $get
方法。如果您在測試文件中不需要 $get
函數中定義的功能,則可以將其賦值為空函數。
如果要在測試文件中加載的模塊需要一堆其他模塊,則除非加載所有必需的模塊,否則無法加載被測模塊。加載所有這些模塊有時會導致測試失敗,因為某些實際的服務方法可能會從測試中調用。為了避免這些困難,我們可以創建虛擬模塊來加載被測模塊。
例如,假設以下代碼表示一個添加了示例服務的模塊:
angular.module('sampleServices', []) .service('util', function() { this.isNumber = function(num) { return !isNaN(num); }; this.isDate = function(date) { return (date instanceof Date); }; });
以下代碼是示例服務的測試文件中的 beforeEach
塊:
module(function($provide) { $provide.service('util', function() { this.isNumber = jasmine.createSpy('isNumber').andCallFake(function(num) { // 模拟实现 }); this.isDate = jasmine.createSpy('isDate').andCallFake(function(num) { // 模拟实现 }); }); }); // 获取模拟服务的引用 var mockUtilSvc; inject(function(util) { mockUtilSvc = util; });
或者,我們也可以將服務的模擬實現添加到上面定義的虛擬模塊中。
如果不使用 Promise,編寫端到端的 Angular 應用程序可能很困難。測試依賴於返回 Promise 的方法的代碼片段成為一項挑戰。普通的 Jasmine 間諜會導致某些測試用例失敗,因為被測函數會期望一個具有實際 Promise 結構的對象。
可以使用另一個返回具有靜態值的 Promise 的異步方法來模擬異步方法。考慮以下工廠:
angular.module('mockingProviders',[]) .provider('sample', function() { var registeredVals = []; this.register = function(val) { registeredVals.push(val); }; this.$get = function() { function getRegisteredVals() { return registeredVals; } return { getRegisteredVals: getRegisteredVals }; }; });
我們將測試上述工廠中的 getData()
函數。正如我們所看到的,它依賴於服務 dataSourceSvc
的方法 getAllItems()
。我們需要在測試 getData()
方法的功能之前模擬服務和方法。
$q
服務具有 when()
和 reject()
方法,允許使用靜態值來解析或拒絕 Promise。這些方法在模擬返回 Promise 的方法的測試中非常有用。以下代碼片段模擬了 dataSourceSvc
工廠:
module(function($provide) { $provide.provider('sample', function() { this.register = jasmine.createSpy('register'); this.$get = function() { var getRegisteredVals = jasmine.createSpy('getRegisteredVals'); return { getRegisteredVals: getRegisteredVals }; }; }); }); // 获取提供程序的引用 var sampleProviderObj; module(function(sampleProvider) { sampleProviderObj = sampleProvider; });
$q
Promise 在下一個 digest 週期後完成其操作。 digest 週期在實際應用程序中不斷運行,但在測試中則不會。因此,我們需要手動調用 $rootScope.$digest()
以強制執行 Promise。以下代碼片段顯示了一個示例測試:
angular.module('first', ['second', 'third']) // util 和 storage 分别在 second 和 third 中定义 .service('sampleSvc', function(utilSvc, storageSvc) { // 服务实现 });
全局對象來自以下來源:
默認情況下,全局對象無法模擬。我們需要遵循某些步驟才能使它們可模擬。
我們可能不想模擬 Math 對像或 _
(由 Underscore 庫創建)的實用程序對象,因為它們的操作不執行任何業務邏輯、不操作 UI,也不與數據源通信。但是,必須模擬諸如 $.ajax、localStorage、WebSockets、breeze 和 toastr 之類對象。因為如果沒有模擬這些對象,這些對象會在執行單元測試時執行其實際操作,這可能會導致一些不必要的 UI 更新、網絡調用,有時還會導致測試代碼中的錯誤。
由於依賴注入,Angular 中編寫的每一部分代碼都是可測試的。 DI 允許我們傳遞任何遵循實際對象 shim 的對象,只是為了使被測代碼在執行時不會中斷。如果可以注入全局對象,則可以模擬它們。有兩種方法可以使全局對象可注入:
$window
注入到需要全局對象的 service/controller 中,並通過 $window
訪問全局對象。例如,以下服務通過 $window
使用 localStorage:angular.module('sampleServices', []) .service('util', function() { this.isNumber = function(num) { return !isNaN(num); }; this.isDate = function(date) { return (date instanceof Date); }; });
module(function($provide) { $provide.service('util', function() { this.isNumber = jasmine.createSpy('isNumber').andCallFake(function(num) { // 模拟实现 }); this.isDate = jasmine.createSpy('isDate').andCallFake(function(num) { // 模拟实现 }); }); }); // 获取模拟服务的引用 var mockUtilSvc; inject(function(util) { mockUtilSvc = util; });
我更喜歡使用常量而不是值來包裝全局對象,因為常量可以注入到配置塊或提供程序中,並且常量不能被裝飾。
以下代碼片段顯示了 localStorage 和 toastr 的模擬:
angular.module('mockingProviders',[]) .provider('sample', function() { var registeredVals = []; this.register = function(val) { registeredVals.push(val); }; this.$get = function() { function getRegisteredVals() { return registeredVals; } return { getRegisteredVals: getRegisteredVals }; }; });
結論
模擬是在任何語言中編寫單元測試的重要組成部分之一。正如我們所看到的,依賴注入在測試和模擬中起著重要作用。代碼必須以一種方式組織,以便輕鬆測試其功能。本文列出了在測試 AngularJS 應用程序時模擬最常見的一組對象。與本文相關的代碼可從 GitHub 下載。
關於在 AngularJS 測試中模擬依賴項的常見問題解答 (FAQ)
在 AngularJS 測試中模擬依賴項是單元測試的關鍵部分。它允許開發人員隔離被測代碼並模擬其依賴項的行為。這樣,您可以測試代碼如何與其依賴項交互,而無需實際調用它們。當依賴項複雜、緩慢或具有您希望在測試期間避免的副作用時,這尤其有用。通過模擬這些依賴項,您可以專注於在受控環境中測試代碼的功能。
在 AngularJS 中創建模擬服務涉及在模塊配置中使用 $provide
服務。您可以使用 $provide
服務的 value
、factory
或 service
方法來定義服務的模擬實現。這是一個基本示例:
module(function($provide) { $provide.provider('sample', function() { this.register = jasmine.createSpy('register'); this.$get = function() { var getRegisteredVals = jasmine.createSpy('getRegisteredVals'); return { getRegisteredVals: getRegisteredVals }; }; }); }); // 获取提供程序的引用 var sampleProviderObj; module(function(sampleProvider) { sampleProviderObj = sampleProvider; });
在這個例子中,我們使用 $provide.value
方法來定義 myService
的模擬實現。在測試期間,將使用此模擬服務代替實際服務。
(其餘的FAQ問題,由於篇幅限制,請逐個提出,我會盡力提供簡潔明了的答案。)
以上是AngularJS測試中的模擬依賴項的詳細內容。更多資訊請關注PHP中文網其他相關文章!