先行一步
VMware 提供培训和认证,助你加速进步。
了解更多在本文中,我们将继续讨论如何在“单页应用”中使用Spring Security与Angular JS。在这里,我们将展示如何使用 Javascript 测试框架Jasmine编写和运行客户端代码的单元测试。这是系列文章的第八篇,你可以通过阅读第一篇文章来了解应用的基本构建块或从头构建,或者你可以直接前往 Github 上的源代码(源代码与第一部分相同,但现在添加了测试)。本文实际上使用的 Spring 或 Spring Security 代码很少,但它涵盖了客户端测试的一种方式,这种方式可能在通常的 Javascript 社区资源中不容易找到,并且我们认为 Spring 的大多数用户会对此感到舒适。
与本系列其余部分一样,构建工具对 Spring 用户来说是典型的,但对经验丰富的前端开发者来说则不然。因此,我们寻求可以使用 Java IDE 并通过熟悉的 Java 构建工具在命令行上使用的解决方案。如果你已经了解 Jasmine 和 Javascript 测试,并且乐于使用基于 Node.js 的工具链(例如 npm
、grunt
等),那么你可能完全可以跳过本文。如果你更习惯于 Eclipse 或 IntelliJ,并且希望前端和后端使用相同的工具,那么本文将对你有所帮助。当我们需要命令行时(例如用于持续集成),我们在示例中使用 Maven,但 Gradle 用户可能会发现相同的代码很容易集成。
提醒:如果你在使用示例应用程序学习本节内容,请务必清除浏览器的 cookie 和 HTTP Basic 凭据缓存。在 Chrome 中,针对单个服务器的最佳方法是打开一个新的隐身窗口。
我们在“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 个重要元素
我们使用一个函数 describe()
来描述被测试的对象(在本例中是“App”)。
在该函数内部,我们提供了几个 beforeEach()
回调,其中一个加载 Angular 模块“hello”,另一个创建一个控制器工厂,我们称之为 $controller
。
行为通过调用 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 中运行它并访问 http://localhost:9999 来查看 Javascript 运行情况。我们提供的一个 @RequestMapping
只是让首页显示我们的测试 HTML。所有(一个)测试都应该是绿色的。
从这里开始,你的开发工作流程将是修改 Javascript 代码,然后在浏览器中重新加载测试应用以运行测试。如此简单!
为了将规范改进到生产级别,我们需要实际断言控制器加载时发生的事情。由于它会调用 $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”示例所需的代码要复杂一些。