只使用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函数

广告
将在 10 秒后关闭
bannerAds