只使用Jasmine进行AngularJS测试
AngularJS测试
在AngularJS的开发者指南中介绍了Jasmine作为Unit测试的工具。你可以从https://docs.angularjs.org/guide/unit-testing 查看相关资料。
此外,在教程中介绍了Jasmine作为Jasmine的执行环境安装karma的方法,但是安装karma需要先安装node.js。
https://docs.angularjs.org/tutorial
出于某种原因,我不想安装Node,所以我尝试了只使用Jasmine(独立版)进行AngularJS的测试。(Jasmine-2.0.1: https://github.com/pivotal/jasmine/blob/master/dist/jasmine-standalone-2.0.1.zip)
├── angular
│ ├── angular-cookies.js
│ ├── angular-mocks.js
│ ├── angular-resource.js
│ ├── angular-route.js
│ ├── angular-sanitize.js
│ └── angular.js
├── app
│ ├── controllers.js
│ └── services.js
├── index.html
└── jasmine
├── MIT.LICENSE
├── SpecRunner.html
├── lib
│ └── jasmine-2.0.1
│ ├── boot.js
│ ├── console.js
│ ├── jasmine-html.js
│ ├── jasmine.css
│ ├── jasmine.js
│ └── jasmine_favicon.png
└── spec
└── sampleControllerSpec.js
从AngularJS的开发者指南中获取了测试对象。(它是一个将通过Yahoo Finance API获取的各国货币汇率乘以输入的数量和金额来显示列表的内容)。
(参考资料:https://docs.angularjs.org/guide/concepts)
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Try Jasmine</title>
<script src="angular/angular.js"></script>
<script src="angular/angular-resource.js"></script>
<script src="angular/angular-cookies.js"></script>
<script src="angular/angular-sanitize.js"></script>
<script src="angular/angular-route.js"></script>
<script src="app/app.js"></script>
<script src="app/controllers.js"></script>
<script src="app/services.js"></script>
</head>
<body ng-app="myapp.controllers">
<h1>AngularJs Sample</h1>
<div class="container" ng-controller="SampleController">
<b>Invoice:</b>
<div>
Quantity: <input type="number" ng-model="qty" required />
</div>
<div>
Costs: <input type="number" ng-model="cost" required /><br />
<select ng-model="inCurr">
<option ng-repeat="c in currencies">{{c}}</option>
</select>
</div>
<div>
<b>Total:</b>
<span ng-repeat="c in currencies">
{{total(c) | currency:c }}
</span><br/>
<button class="btn" ng-click="pay()">Pay</button>
</div>
</div>
</body>
</html>
angular.module('myapp.controllers', ['myapp.services'])
.controller('SampleController', function($scope, SampleService) {
$scope.qty = 1;
$scope.cost = 2;
$scope.inCurr = 'JPY';
$scope.currencies = SampleService.currencies;
$scope.total = function (outCurr) {
return SampleService.convert($scope.qty * $scope.cost, $scope.inCurr, outCurr);
};
$scope.pay = function () {
window.alert("Thanks!");
};
});
angular.module('myapp.services', [])
.factory('SampleService', function ($http) {
var YAHOO_FINANCE_URL_PATTERN =
'http://query.yahooapis.com/v1/public/yql?q=select * from '+
'yahoo.finance.xchange where pair in ("PAIRS")&format=json&'+
'env=store://datatables.org/alltableswithkeys&callback=JSON_CALLBACK';
var currencies = ['USD', 'EUR', 'CNY', 'JPY'];
var usdToForeignRates = {};
refresh();
return {
currencies: currencies,
convert: convert,
refresh: refresh
};
function convert(amount, inCurr, outCurr) {
return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
};
function refresh() {
var url = YAHOO_FINANCE_URL_PATTERN.replace('PAIRS', 'USD' + currencies.join('","USD'));
return $http.jsonp(url).success(
function(data) {
var newUsdToForeignRates = {};
angular.forEach(data.query.results.rate, function(rate) {
var currency = rate.id.substring(3,6);
newUsdToForeignRates[currency] = window.parseFloat(rate.Rate);
});
usdToForeignRates = newUsdToForeignRates;
});
};
});
考试
在SpecRunner.html文件中进行了对使用的库进行定义。
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Jasmine Spec Runner v2.0.1</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-2.0.1/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.1/jasmine.css">
<script type="text/javascript" src="lib/jasmine-2.0.1/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-2.0.1/jasmine-html.js"></script>
<script type="text/javascript" src="lib/jasmine-2.0.1/boot.js"></script>
<script type="text/javascript" src="../angular/angular.js"></script>
<script type="text/javascript" src="../angular/angular-mocks.js"></script>
<script type="text/javascript" src="../angular/angular-resource.js"></script>
<script type="text/javascript" src="../angular/angular-cookies.js"></script>
<script type="text/javascript" src="../angular/angular-sanitize.js"></script>
<script type="text/javascript" src="../angular/angular-route.js"></script>
<!-- include source files here... -->
<script type="text/javascript" src="../app/controllers.js"></script>
<script type="text/javascript" src="../app/services.js"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="spec/SampleControllerSpec.js"></script>
</head>
<body>
</body>
</html>
进行文件分析时,不得不针对每个文件定义source和spec,这真是太麻烦了。
这个库的定义几乎与index.html相同,唯一需要定义的是用于测试的angular-mocks.js。
如果没有定义,会显示ReferenceError: module is not defined的错误消息。
由于错误消息不太明确,所以导致了困扰。
写完对SampleController的测试之后就结束了。
'use strict';
describe('Controller: SampleController', function () {
// load the controller's module
beforeEach(module('myapp.controllers'));
var ctrl,
scope;
var expected = [ 'USD', 'EUR', 'CNY', 'JPY' ];
var usdToForeignRates = [];
usdToForeignRates['USD'] = 0.01;
usdToForeignRates['EUR'] = 0.01;
usdToForeignRates['CYN'] = 0.06;
usdToForeignRates['JPY'] = 1.00;
var alert_msg;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, SampleService) {
scope = $rootScope.$new();
ctrl = $controller('SampleController', {
$scope: scope
});
spyOn(SampleService, 'convert').and.callFake(function(amount, inCurr, outCurr) {
return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
});
alert_msg = '_defalut_';
spyOn(window, 'alert').and.callFake(function(msg) {
alert_msg = msg;
});
}));
it('should attach a list of currencies to the scope', function () {
expect(scope.currencies.length).toBe(4);
expect(scope.currencies).toEqual(expected);
});
it('when total() is called returns the calculated value', function () {
expect(scope.total('USD')).toBe(0.02);
expect(scope.total('EUR')).toBe(0.02);
expect(scope.total('CYN')).toBe(0.12);
expect(scope.total('JPY')).toBe(2);
});
it('when pay() is called returns the correct alert message', function () {
expect(alert_msg).toEqual('_defalut_');
scope.pay();
expect(alert_msg).toEqual('Thanks!');
});
});
简单来说,首先按照例子定义要在第一个beforeEach中使用的模块。接下来,在下一个beforeEach中生成范围和要测试的控制器ctrl。
此外,在这里,我们使用了Jasmine的Spy功能来定义一个替代对象,以用于SampleService的convert函数。这是因为convert函数使用了外部调用的结果。此外,我们还使用了Spy功能来确认alert的消息。Spy功能非常方便。
请参考
以AngularJS编写单元测试
使用Jasmine的Spy功能创建测试替身
在Angular.js中对$http服务进行单元测试
警告!使用Jasmine测试Javascript的alert函数