測試 Angular 應用:Angular JS 和 Spring Security 系列第八篇

工程 | Dave Syer | 2015年5月19日 | ...

在本篇文章中,我們將繼續 討論 如何在“單頁應用”中使用 Spring SecurityAngular JS。在這裡,我們將展示如何使用 Javascript 測試框架 Jasmine 來編寫和執行客戶端程式碼的單元測試。這是系列文章中的第八篇,您可以閱讀 第一篇文章 來回顧應用的基本構建塊或從零開始構建,或者直接檢視 Github 上的原始碼(與第一部分是相同的原始碼,但現在添加了測試)。本文實際使用的 Spring 或 Spring Security 程式碼非常少,但它以一種在常見 Javascript 社群資源中不太容易找到的方式涵蓋了客戶端測試,並且我們認為這種方式對於大多數 Spring 使用者來說會比較熟悉。

與本系列的其餘部分一樣,構建工具對於 Spring 使用者來說是典型的,對於經驗豐富的前端開發人員來說則不太常見。因此,我們尋找可以從 Java IDE 和命令列使用熟悉的 Java 構建工具的解決方案。如果您已經瞭解 Jasmine 和 Javascript 測試,並且樂於使用基於 Node.js 的工具鏈(例如 npmgrunt 等),那麼您可以完全跳過本文。如果您更習慣使用 Eclipse 或 IntelliJ,並且希望為前端和後端使用相同的工具,那麼本文將引起您的興趣。當我們需要命令列(例如用於持續整合)時,我們在示例中使用 Maven,但 Gradle 使用者可能會發現將相同程式碼整合起來很容易。

提醒:如果您正在使用示例應用程式來學習本節內容,請確保清除瀏覽器快取中的 cookie 和 HTTP Basic 憑據。在 Chrome 中,對於單個伺服器,最好的方法是開啟一個新的無痕視窗。

使用 Jasmine 編寫規範

“basic”應用中的“home”控制器非常簡單,因此無需太多就可以對其進行徹底測試。以下是程式碼的提醒(hello.js

angular.module('hello', []).controller('home', function($scope, $http) {
  $http.get('resource/').success(function(data) {
    $scope.greeting = data;
  })
});

我們面臨的主要挑戰是在測試中提供 $scope$http 物件,以便我們可以斷言它們在控制器中的使用方式。實際上,即使在我們面臨這一挑戰之前,我們也需要能夠建立一個控制器例項,以便測試它載入時會發生什麼。方法如下。

建立一個新檔案 spec.js 並將其放在“src/test/resources/static/js”中

describe("App", function() {

	beforeEach(module('hello'));

    var $controller;
	beforeEach(inject(function($injector) {
		$controller = $injector.get('$controller');
	}));

	it("loads a controller", function() {
		var controller = $controller('home')
	});

}

在這個非常基礎的測試套件中,我們有 3 個重要元素

  1. 我們使用一個函式來 describe() 被測試的物件(本例中為“App”)。

  2. 在該函式內部,我們提供了幾個 beforeEach() 回撥,其中一個載入 Angular 模組“hello”,另一個建立一個控制器工廠,我們稱之為 $controller

  3. 行為透過呼叫 it() 來表達,我們在其中用文字說明期望是什麼,然後提供一個進行斷言的函式。

這裡的測試函式非常簡單,實際上甚至沒有進行斷言,但它建立了“home”控制器的例項,所以如果它失敗,測試就會失敗。

注意:在 Java 應用中,“src/test/resources/static/js”是測試程式碼的邏輯位置,儘管也可以選擇“src/test/javascript”。稍後我們將看到為什麼將其放在測試類路徑中有意義(事實上,如果您習慣了 Spring Boot 的約定,您可能已經明白了為什麼)。

現在我們需要一個 Javascript 程式碼的驅動程式,形式為一個可以在瀏覽器中載入的 HTML 頁面。建立一個名為“test.html”的檔案並將其放在“src/test/resources/static”中

<!doctype html>
<html>
<head>

<title>Jasmine Spec Runner</title>
<link rel="stylesheet" type="text/css"
  href="/webjars/jasmine/2.0.0/jasmine.css">
<script type="text/javascript" src="/webjars/jasmine/2.0.0/jasmine.js"></script>
<script type="text/javascript"
  src="/webjars/jasmine/2.0.0/jasmine-html.js"></script>
<script type="text/javascript" src="/webjars/jasmine/2.0.0/boot.js"></script>

<!-- include source files here... -->
<script type="text/javascript" src="/js/angular-bootstrap.js"></script>
<script type="text/javascript" src="/js/hello.js"></script>

<!-- include spec files here... -->
<script type="text/javascript"
  src="/webjars/angularjs/1.3.8/angular-mocks.js"></script>
<script type="text/javascript" src="/js/spec.js"></script>

</head>

<body>
</body>
</html>

HTML 內容為空,但它載入了一些 Javascript,並且指令碼全部執行後會有一個 UI。

首先,我們從 /webjars/** 載入所需的 Jasmine 元件。我們載入的 4 個檔案只是樣板程式碼 - 您可以對任何應用程式執行相同的操作。為了在測試中執行時使其可用,我們需要將 Jasmine 依賴項新增到我們的“pom.xml”中

<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>jasmine</artifactId>
  <version>2.0.0</version>
  <scope>test</scope>
</dependency>

然後我們來到特定於應用程式的程式碼。我們前端的主要原始碼是“hello.js”,所以我們必須載入它,以及它的依賴項,形式為“angular-bootstrap.js”(後者由 wro4j maven 外掛建立,因此您需要先成功執行 mvn package 一次才能載入它)。

最後,我們需要剛剛編寫的“spec.js”及其依賴項(任何尚未包含在其他指令碼中的),對於 Angular 應用程式,這幾乎總是包括“angular-mocks.js”。我們從 webjars 載入它,因此您也需要將該依賴項新增到“pom.xml”中

<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>angularjs</artifactId>
  <version>1.3.8</version>
  <scope>test</scope>
</dependency>

注意:angularjs webjar 已經是 wro4j 外掛的依賴項,以便它可以構建“angular-bootstrap.js”。這將用於不同的構建步驟,因此我們需要它再次。

執行規範

要執行我們的“test.html”程式碼,我們需要一個微型應用程式(例如,在“src/test/java/test”中)

@SpringBootApplication
@Controller
public class TestApplication {

	@RequestMapping("/")
	public String home() {
		return "forward:/test.html";
	}

	public static void main(String[] args) {
		new SpringApplicationBuilder(TestApplication.class).properties(
				"server.port=9999", "security.basic.enabled=false").run(args);
	}

}

TestApplication 是純樣板程式碼:所有應用程式都可以以相同的方式執行測試。您可以在 IDE 中執行它,然後訪問 https://:9999 來檢視 Javascript 正在執行。我們提供的唯一 @RequestMapping 只是使主頁顯示我們的測試 HTML。所有(一個)測試都應該是綠色的。

從這裡開始,您的開發人員工作流程將是進行 Javascript 程式碼的更改,然後在瀏覽器中重新載入測試應用程式以執行測試。如此簡單!

改進單元測試:模擬 HTTP 後端

為了使規範達到生產級別,我們需要實際斷言控制器載入時會發生什麼。由於它呼叫了 $http.get(),我們需要模擬該呼叫,以避免僅為單元測試而執行整個應用程式。為此,我們使用 Angular 的 $httpBackend(在“spec.js”中)

describe("App", function() {

  beforeEach(module('hello'));

  var $httpBackend, $controller;
  beforeEach(inject(function($injector) {
    $httpBackend = $injector.get('$httpBackend');
    $controller = $injector.get('$controller');
  }));

  afterEach(function() {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });

  it("says Hello Test when controller loads", function() {
    var $scope = {};
    $httpBackend.expectGET('resource/').respond(200, {
      id : 4321,
      content : 'Hello Test'
    });
    var controller = $controller('home', {
      $scope : $scope
    });
    $httpBackend.flush();
    expect($scope.greeting.content).toEqual('Hello Test');
  });

})

這裡新增的部分是

  • beforeEach() 中建立 $httpBackend

  • 新增一個新的 afterEach() 來驗證後端的狀態。

  • 在測試函式中,我們在建立控制器之前設定後端的期望,告訴它期望對“resource/”的呼叫,以及響應應該是什麼。

  • 我們還添加了一個對 jasmine expect() 的呼叫來斷言結果。

無需啟動和停止測試應用程式,此測試現在在瀏覽器中應該是綠色的。

在命令列中執行規範

能夠在瀏覽器中執行規範很棒,因為現代瀏覽器內建了出色的開發工具(例如 Chrome 中的 F12)。您可以設定斷點並檢查變數,還可以重新整理檢視以在即時伺服器中重新執行測試。但這無助於持續整合:為此,您需要一種從命令列執行測試的方法。無論您選擇哪種構建工具,都有可用的工具,但由於我們在這裡使用 Maven,我們將向“pom.xml”新增一個外掛

<plugin>
  <groupId>com.github.searls</groupId>
  <artifactId>jasmine-maven-plugin</artifactId>
  <version>2.0-alpha-01</version>
  <executions>
    <execution>
      <goals>
        <goal>test</goal>
      </goals>
    </execution>
  </executions>
</plugin>

此外掛的預設設定將無法與我們已經設定好的靜態資源佈局一起工作,因此我們需要對其進行一些配置

<plugin>
  ...
  <configuration>
    <additionalContexts>
      <context>
        <contextRoot>/lib</contextRoot>
        <directory>${project.build.directory}/generated-resources/static/js</directory>
      </context>
    </additionalContexts>
    <preloadSources>
      <source>/lib/angular-bootstrap.js</source>
      <source>/webjars/angularjs/1.3.8/angular-mocks.js</source>
    </preloadSources>
    <jsSrcDir>${project.basedir}/src/main/resources/static/js</jsSrcDir>
    <jsTestSrcDir>${project.basedir}/src/test/resources/static/js</jsTestSrcDir>
    <webDriverClassName>org.openqa.selenium.phantomjs.PhantomJSDriver</webDriverClassName>
  </configuration>
</plugin>

請注意,webDriverClassName 指定為 PhantomJSDriver,這意味著您需要在執行時將 phantomjs 放在 PATH 中。這在 Travis CI 中開箱即用,並且在 Linux、MacOS 和 Windows 中需要簡單的安裝 - 您可以 下載二進位制檔案 或使用包管理器,例如 Ubuntu 上的 apt-get。原則上,任何 Selenium WebDriver 都可以在此處使用(預設是 HtmlUnitDriver),但對於 Angular 應用程式,PhantomJS 可能是最好的選擇。

我們還需要使 Angular 庫可供外掛使用,以便它可以載入“angular-mocks.js”依賴項

<plugin>
  ...
  <dependencies>
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>angularjs</artifactId>
      <version>1.3.8</version>
    </dependency>
  </dependencies>
</plugin>

就是這樣。再次都是樣板程式碼(因此您可以將其放在父 POM 中,如果您想在多個專案之間共享程式碼)。只需在命令列中執行它

$ mvn jasmine:test

測試也作為 Maven 的“test”生命週期的一部分執行,因此您只需執行 mvn test 即可執行所有 Java 測試以及 Javascript 測試,從而非常順暢地融入您現有的構建和部署週期。這是日誌

$ mvn test
...
[INFO] 
-------------------------------------------------------
 J A S M I N E   S P E C S
-------------------------------------------------------
[INFO] 
App
  says Hello Test when controller loads

Results: 1 specs, 0 failures

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 21.064s
[INFO] Finished at: Sun Apr 26 14:46:14 BST 2015
[INFO] Final Memory: 47M/385M
[INFO] ------------------------------------------------------------------------

Jasmine Maven 外掛還帶有一個目標 mvn jasmine:bdd,該目標執行一個您可以在瀏覽器中載入以執行測試的伺服器(作為上述 TestApplication 的替代方案)。

結論

能夠為 Javascript 執行單元測試在現代 Web 應用程式中非常重要,而這是我們在本系列中一直忽略(或迴避)的主題。透過本期,我們介紹瞭如何編寫測試、如何在開發時執行測試以及最重要的是,如何在持續整合環境中執行測試的基本要素。我們採取的方法不一定適合所有人,所以請不要因為您以不同的方式處理它而感到沮喪,但請確保您擁有所有這些基本要素。我們在這裡的方式可能會讓傳統的 Java 企業開發者感到熟悉,並且能很好地與他們現有的工具和流程整合,因此如果您屬於這一類,我希望您能將它作為起點。您可以在網際網路上找到大量關於使用 Angular 和 Jasmine 進行測試的更多示例,但首選的起點可能是本系列中的 “single”示例,該示例現在有一些最新的測試程式碼,這些程式碼比我們在這篇文章中為“basic”示例編寫的程式碼要複雜一些。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有