AngularJS測試中的模擬依賴項
核心要點
- AngularJS 天生就考慮到了測試,其內置的依賴注入機制使得每個組件都可使用任何 JavaScript 測試框架(如 Jasmine)進行測試。
- 單元測試中的模擬涉及隔離測試代碼片段的功能,這可能具有挑戰性,因為依賴項來自不同的來源。 AngularJS 中的模擬通過
angular-mocks
模塊簡化了,該模塊為一組常用的 AngularJS 服務提供了模擬。 - AngularJS 中的服務模擬可以通過獲取實際服務的實例並偵聽服務的方法,或者使用
$provide
實現模擬服務來完成。後者方法更可取,可以避免調用服務的實際方法實現。 - AngularJS 中的提供程序模擬遵循與服務模擬類似的規則。測試中必須實現
$get
方法。如果測試文件中不需要$get
函數中定義的功能,則可以將其賦值為空函數。 - 全局對象(例如全局“window”對象的一部分或由第三方庫創建的對象)可以通過注入它們到
$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 的方法
如果不使用 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) { // 服务实现 });
模擬全局對象
全局對象來自以下來源:
- 全局“window”對象的一部分的對象(例如,localStorage、indexedDb、Math 等)。
- 由第三方庫(如 jQuery、underscore、moment、breeze 或任何其他庫)創建的對象。
默認情況下,全局對象無法模擬。我們需要遵循某些步驟才能使它們可模擬。
我們可能不想模擬 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); }; });
- 使用全局對象創建一個值或常量,並在需要的地方注入它。例如,以下代碼是 toastr 的常量:
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 測試中模擬依賴項是單元測試的關鍵部分。它允許開發人員隔離被測代碼並模擬其依賴項的行為。這樣,您可以測試代碼如何與其依賴項交互,而無需實際調用它們。當依賴項複雜、緩慢或具有您希望在測試期間避免的副作用時,這尤其有用。通過模擬這些依賴項,您可以專注於在受控環境中測試代碼的功能。
如何在 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中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。
