首頁 > web前端 > js教程 > AngularJS測試中的模擬依賴項

AngularJS測試中的模擬依賴項

Jennifer Aniston
發布: 2025-02-20 12:28:16
原創
361 人瀏覽過

Mocking Dependencies in AngularJS Tests

核心要點

  • 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) {
    // 服务实现
  });
登入後複製

模擬全局對象

全局對象來自以下來源:

  1. 全局“window”對象的一部分的對象(例如,localStorage、indexedDb、Math 等)。
  2. 由第三方庫(如 jQuery、underscore、moment、breeze 或任何其他庫)創建的對象。

默認情況下,全局對象無法模擬。我們需要遵循某些步驟才能使它們可模擬。

我們可能不想模擬 Math 對像或 _(由 Underscore 庫創建)的實用程序對象,因為它們的操作不執行任何業務邏輯、不操作 UI,也不與數據源通信。但是,必須模擬諸如 $.ajax、localStorage、WebSockets、breeze 和 toastr 之類對象。因為如果沒有模擬這些對象,這些對象會在執行單元測試時執行其實際操作,這可能會導致一些不必要的 UI 更新、網絡調用,有時還會導致測試代碼中的錯誤。

由於依賴注入,Angular 中編寫的每一部分代碼都是可測試的。 DI 允許我們傳遞任何遵循實際對象 shim 的對象,只是為了使被測代碼在執行時不會中斷。如果可以注入全局對象,則可以模擬它們。有兩種方法可以使全局對象可注入:

  1. $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);
    };
  });
登入後複製
登入後複製
登入後複製
  1. 使用全局對象創建一個值或常量,並在需要的地方注入它。例如,以下代碼是 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 服務的 valuefactoryservice 方法來定義服務的模擬實現。這是一個基本示例:

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

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板