核心要點
- 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中文網其他相關文章!

JavaScript框架的強大之處在於簡化開發、提升用戶體驗和應用性能。選擇框架時應考慮:1.項目規模和復雜度,2.團隊經驗,3.生態系統和社區支持。

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

Node.js擅長於高效I/O,這在很大程度上要歸功於流。 流媒體匯總處理數據,避免內存過載 - 大型文件,網絡任務和實時應用程序的理想。將流與打字稿的類型安全結合起來創建POWE

Python和JavaScript在性能和效率方面的差異主要體現在:1)Python作為解釋型語言,運行速度較慢,但開發效率高,適合快速原型開發;2)JavaScript在瀏覽器中受限於單線程,但在Node.js中可利用多線程和異步I/O提升性能,兩者在實際項目中各有優勢。

JavaScript起源於1995年,由布蘭登·艾克創造,實現語言為C語言。 1.C語言為JavaScript提供了高性能和系統級編程能力。 2.JavaScript的內存管理和性能優化依賴於C語言。 3.C語言的跨平台特性幫助JavaScript在不同操作系統上高效運行。

JavaScript在瀏覽器和Node.js環境中運行,依賴JavaScript引擎解析和執行代碼。 1)解析階段生成抽象語法樹(AST);2)編譯階段將AST轉換為字節碼或機器碼;3)執行階段執行編譯後的代碼。

Python和JavaScript的未來趨勢包括:1.Python將鞏固在科學計算和AI領域的地位,2.JavaScript將推動Web技術發展,3.跨平台開發將成為熱門,4.性能優化將是重點。兩者都將繼續在各自領域擴展應用場景,並在性能上有更多突破。

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。


熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

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

熱門文章

熱工具

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

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

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),