使用Jasmine和Karma对AngularJS页面程序进行测试
AngularJS是继jQuery之后发生在JavaScript上最好的东西。这也是JavaScript开发一直以来想要的方式。Angular主要的优点之一就是它的依赖注入(DependencyInjection),它非常利于代码的单元测试。但有点小怪异的是,我在无论如何都没能找到一个介绍如何做单元测试的教程。
当然有很多不错的推荐:使用Jasmine测试框架和Karma测试执行器(TestRunner);但是并没有一篇完整的从无到有指导如何测试的教程。所以我写了这篇文章。我在网上找了很多资源才知道如何去做,而你现在不需要去做这些(如果一开始就看到这篇文章的话)。
请告诉我你看到的任何错误,直到我能说这是基于Karma和Jasmine测试Angular应用的最佳实践。
介绍
这篇文章会引导你安装使用Karma和Jasmine做自动化测试所需要的所有工具。我不在乎你实在使用TDD(测试驱动开发)还是TAD(测试辅助开发),在这篇文章中,我假设你已经有一个文件需要测试。
安装Karma
如果你没有安装Node.js,那么请自行下载和安装。安装之后,打开终端或命令行输入一下命令:
npminstall-gkarma
文件结构
文件结构是跟我们的议题关联不大,但是在接下来的测试中,我使用的文件结构如下:
Application |angular.js |angular-resource.js |Home |home.js |Tests |Home |home.tests.js |karma.config.js(willbecreatedinthenextstep) |angular-mocks.js
*我并不主张这种文档结构,我展示它只是为了测试举例。
配置Karma
切换到你想要放置配置文件的目录,然后在终端中输入下面的命令来创建配置文件:
karmainitkarma.config.js
你会被询问一些问题,包括你想使用那个测试框架,你是否需要自动监测文件,包含哪些测试和被测试文件等。在我们的教程中,我们保留‘Jasmine'作为我们默认的框架,开启文件自动监测,并包含下面的文件:
../*.js ../**.*.js angular-mocks.js **/*.tests.js
这些都是相对路径,包含了1)父目录下的所有.js文件,2)父目录下的所有子目录下的所有.js文件,3)当前目录下的angular-mock.js,4)以及当前目录(包含子目录)下所有的.tests.js文件(我喜欢以这样的方式来区分测试文件和其他的文件)。
不管你选择了什么文件,请确保你引入了angular.js,angular-mock.js,以及其他你需要使用的文件。
启动Karma
现在已经可以启动Karma了,依然在终端中输入:
karmastartkarma.config.js
这个命令会在你的电脑上启动你在配置文件中列出的浏览器。这些浏览器都会以socket的方式连接到Karma的实例上,你会看到一组活动的浏览器并被告知她们是否在执行测试。我但愿Karma已经告诉你在每个浏览器上的最终测试结果总结(比如16个中的15个通过,1个失败),遗憾的是你只能通过终端窗口看到这些信息。
Karma的一个很突出的特色是你可以在网络中使用任何设备连接并测试你的代码。试一下将你的手机浏览器指向Karma服务,你可以在电脑上任何一个运行的浏览器上找到这个测试的URL地址。它应该类似于:http://localhost:9876/?id=5359192。你可以将你的手机,虚拟机,或其他任何设备的浏览器指向[你在网络上的IP地址]:9876/?id=5359192.因为Karma是在运行一个Node.js实例,你的测试机器就像一个web服务器一样,会将测试发送到任何指向它的浏览器。
基本的测试
我们假设你已经有一个文件需要测试。我们要使用到的home.js文件如下:
home.js
'usestrict'; varapp=angular.module('Application',['ngResource']); app.factory('UserFactory',function($resource){ return$resource('Users/users.json') }); app.controller('MainCtrl',function($scope,UserFactory){ $scope.text='HelloWorld!'; $scope.users=UserFactory.get(); });
我们可以在home.test.js文件中创建我们的测试用例。我们从简单的那个测试开始:$scope.text应该等于‘HelloWorld!'。为了完成这个测试,我们需要模拟我们的Application模块以及$scope变量。我们会在Jasmine的beforeEach方法中做这个工作,这样的话我们在每个测试用例开始时可以有一个全新的(干净的)controler和scope对象。
home.tests.js
'usestrict'; describe('MainCtrl',function(){ varscope; //我们会在测试中使用这个scope //模拟我们的Application模块并注入我们自己的依赖 beforeEach(angular.mock.module('Application')); //模拟Controller,并且包含$rootScope和$controller beforeEach(angular.mock.inject(function($rootScope,$controller){ //创建一个空的scope scope=$rootScope.$new(); //声明Controller并且注入已创建的空的scope $controller('MainCtrl',{$scope:scope}); }); //测试从这里开始 });
从代码中你可以看到我们注入了我们自己的scope,因此我们可以在它的外部验证它的信息。同时,别忘了模拟模块本身(第7行代码)!我们现在已经为测试做好了准备:
home.tests.js
//测试从这里开始 it('shouldhavevariabletext="HelloWorld!"',function(){ expect(scope.text).toBe('HelloWorld!); });
如果你运行这个测试,它可以在任何指向Karma的浏览器中执行,并且测试通过。
发送$resource请求
现在我们已经准备好测试$resource请求。要完成这个请求,我们需要使用到$httpBackend,它一个模拟版本的Angular$http。我们会创建另一个叫做$httpBackend的变量,在第二个beforEach块中,注入_$httpBackend_并将新创建的变量指向_$httpBackend_。接下来我们会告诉$httpBackend如何对请求做出响应。
$httpBackend=_$httpBackend_; $httpBackend.when('GET','Users/users.json').respond([{id:1,name:'Bob'},{id:2,name:'Jane'}]);
我们的测试:home.tests.js
it('shouldfetchlistofusers',function(){ $httpBackend.flush(); expect(scope.users.length).toBe(2); expect(scope.users[0].name).toBe('Bob'); });
都放到一起
home.tests.js
'usestrict'; describe('MainCtrl',function(){ varscope,$httpBackend; //we'llusetheseinourtests //mockApplicationtoallowustoinjectourowndependencies beforeEach(angular.mock.module('Application')); //mockthecontrollerforthesamereasonandinclude$rootScopeand$controller beforeEach(angular.mock.inject(function($rootScope,$controller,_$httpBackend_){ $httpBackend=_$httpBackend_; $httpBackend.when('GET','Users/users.json').respond([{id:1,name:'Bob'},{id:2,name:'Jane'}]); //createanemptyscope scope=$rootScope.$new(); //declarethecontrollerandinjectouremptyscope $controller('MainCtrl',{$scope:scope}); }); //testsstarthere it('shouldhavevariabletext="HelloWorld!"',function(){ expect(scope.text).toBe('HelloWorld!'); }); it('shouldfetchlistofusers',function(){ $httpBackend.flush(); expect(scope.users.length).toBe(2); expect(scope.users[0].name).toBe('Bob'); }); });
技巧
Karma会运行所有文件中的所有测试用例,如果你只想运行所有测试的一个子集,修改describe或it为ddescribe或iit来运行个别的一些测试。如果有些测试你不想运行他们,那么修改describe或it为xdescribe或xit来忽略这些代码。
你也可以在html文件的页面上运行你的测试。举例的代码如下:
home.runner.html
<!DOCTYPEhtml> <html> <head> <title>PartnerSettingsTestSuite</title> <!--includeyourscriptfiles(noticethatthejasminesourcefileshavebeenaddedtotheproject)--> <scripttype="text/javascript"src="../jasmine/jasmine-1.3.1/jasmine.js"></script> <scripttype="text/javascript"src="../jasmine/jasmine-1.3.1/jasmine-html.js"></script> <scripttype="text/javascript"src="../angular-mocks.js"></script> <scripttype="text/javascript"src="home.tests.js"></script> <linkrel="stylesheet"href="../jasmine/jasmine-1.3.1/jasmine.css"/> </head> <body> <!--useJasminetorunanddisplaytestresults--> <scripttype="text/javascript"> varjasmineEnv=jasmine.getEnv(); jasmineEnv.addReporter(newjasmine.HtmlReporter()); jasmineEnv.execute(); </script> </body> </html>