领先一步
VMware提供培训和认证,以加速您的进步。
了解更多在本文中,我们将继续讨论如何在“单页应用程序”中将Spring Security与AngularJS一起使用。在这里,我们将展示如何使用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')
});
}
在这个非常基本的测试套件中,我们有三个重要的元素
我们使用一个函数来describe()
正在测试的内容(在本例中为“App”)。
在这个函数内部,我们提供几个beforeEach()
回调函数,其中一个加载Angular模块“hello”,另一个为控制器创建一个工厂,我们称之为$controller
。
行为通过调用it()
来表达,我们在其中用文字说明期望是什么,然后提供一个进行断言的函数。
这里的测试函数非常简单,实际上甚至没有进行断言,但它确实创建了一个“home”控制器的实例,因此如果失败,则测试将失败。
注意:“src/test/resources/static/js”是在Java应用程序中进行测试代码的逻辑位置,尽管可以将其放在“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组件。我们加载的四个文件只是样板——您可以对任何应用程序执行相同的操作。为了在测试运行时使它们可用,我们需要将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://127.0.0.1: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
),但PhantomJS可能是用于Angular应用程序的最佳驱动程序。
我们还需要使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”示例编写的代码更不那么琐碎。