測試 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”控制器的一個例項,所以如果建立失敗,測試就會失敗。

注意:“src/test/resources/static/js”是在 Java 應用中放置測試程式碼的邏輯位置,儘管也可以放在“src/test/javascript”。我們稍後將看到為什麼將其放在測試 classpath 中有意義(實際上,如果你習慣於 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 web 驅動程式都可以在這裡使用(預設是 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 的替代方案)。

結論

能夠在現代 Web 應用中執行 Javascript 單元測試非常重要,這是我們在本系列中一直忽視(或迴避)的話題。在本篇中,我們介紹瞭如何編寫測試、如何在開發時執行測試以及更重要的是,如何在持續整合環境中執行測試的基本要素。我們採用的方法並不適合所有人,所以如果採用不同的方式,請不要感到不好,但請確保你具備所有這些要素。我們這裡介紹的方法對於傳統的 Java 企業開發者來說可能會感到舒適,並且能夠很好地與他們現有的工具和流程整合,所以如果你屬於這一類,我希望你會發現它是一個有用的起點。網際網路上有很多關於使用 Angular 和 Jasmine 進行測試的更多示例,但首先可以檢視本系列中的“single”示例,其中包含一些更新的測試程式碼,這些程式碼比本文中“basic”示例所需的程式碼要複雜一些。

訂閱 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

先行一步

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

瞭解更多

獲取支援

Tanzu Spring 在一個簡單的訂閱中提供對 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

即將舉行的活動

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

檢視全部