【问题标题】:AngularJS + CanvasJS: how to unit test?AngularJS + CanvasJS:如何进行单元测试?
【发布时间】:2014-08-21 05:07:50
【问题描述】:

我正在尝试在使用 Yeoman 搭建的 AngularJS 应用程序中运行单元测试。 我尝试运行的唯一测试只是检查和数组是否具有正确的长度但失败 因为与 CanvasJS 相关的事情

这里是日志

LOG: 'CanvasJS Error: Chart Container with id "chartContainer" was not found'
PhantomJS 1.9.7 (Windows 7) Controller: MainCtrl should attach a list of inputs to the scope  FAILED
        TypeError: 'undefined' is not an object (evaluating 'this._toolBar.appendChild')
            at /path/to/Chart/app/scripts/vendor/canvasjs-1.5.0-beta/source/canvasjs.js:1799
            at /path/to/Chart/app/scripts/vendor/canvasjs-1.5.0-beta/source/canvasjs.js:1919
            at /path/to/Chart/app/scripts/vendor/canvasjs-1.5.0-beta/source/canvasjs.js:2093
            at /path/to/Chart/app/scripts/vendor/canvasjs-1.5.0-beta/source/canvasjs.js:12551
            at /path/to/Chart/app/scripts/controllers/main.js:101
            at invoke (/path/to/Chart/app/bower_components/angular/angular.js:3869)
            at instantiate (/path/to/Chart/app/bower_components/angular/angular.js:3880)
            at /path/to/Chart/app/bower_components/angular/angular.js:7134
            at /path/to/Chart/test/spec/controllers/main.js:15
            at invoke (/path/to/Chart/app/bower_components/angular/angular.js:3869)
            at workFn (/path/to/Chart/app/bower_components/angular-mocks/angular-mocks.js:2147)
        undefined
        Expected 0 to be 3.
PhantomJS 1.9.7 (Windows 7): Executed 1 of 1 (1 FAILED) ERROR (0.025 secs / 0.021 secs)
Warning: Task "karma:unit" failed. Use --force to continue.

应用程序可以在这里找到https://github.com/kaiohken1982/Chart

我不确定我是否在 AngularJS 应用程序中正确实施了这个库,非常感谢您的建议。

谢谢

------------------- 编辑 #1 --------------------

下面的一些代码可以更好地理解场景,正如你所看到的 CanvasJS 是 用作全局而不是作为服务注入,所以问题之一是如果没有凉亭回购,以这种方式使用第三方库是否正确。

我认为是这条线导致了问题

...
$scope.chart = new CanvasJS.Chart('chartContainer', {
...

控制器视图包含“chartContainer”id

<div id="chartContainer"></div>

app/scripts/controllers/main.js '使用严格';

/*global CanvasJS:false */

/**
 * @ngdoc function
 * @name ngprojectApp.controller:MainCtrl
 * @description
 * # MainCtrl
 * Controller of the ngprojectApp
 */
angular.module('ChartApp')
  .controller('MainCtrl', function ($scope) {

    // Regexp to ensure that input dates are in the required format
    $scope.dateRegexp = /^(January|February|March|April|June|July|August|Semptember|October|November|December)[ ](0[1-9]|1[0-9]|2[0-9]|3[0-1])[ ](19|20)\d{2}$/i;

    // Called to remove an entry
    $scope.removeInput = function (index) {
      $scope.inputs.splice(index, 1);
    };

    // Called to add an entry
    $scope.addRow = function () {
      $scope.inputs.push($scope.value);
      $scope.value = '';
    };

     $scope.predicate = '';
     $scope.reverse = false;
     $scope.inputs = [];
     $scope.updateMyText = function() {};

     // Chart below
     var datapoints = [];

     var chartPoint = null;
     $scope.canEdit = true;
     $scope.editMode = false;
     $scope.units = 1; // increase or decrease bar value by this unit
     $scope.chart = new CanvasJS.Chart('chartContainer', {
       theme: 'theme1',
       interactivityEnabled: true,
       title:{
          text: 'Website response time'              
       },
       axisY: {
          title: 'ms',
          labelFontSize: 16,
       },
       axisX: {
          title: 'timeline',
          labelFontSize: 16,
          gridThickness: 1
       },
       data: [              
          {
            click: function() {
              $scope.$apply(function() {
                if($scope.canEdit) {
                  $scope.editMode = !$scope.editMode; 
                }
              });

              if($scope.editMode) {
                console.log('EditMode is now TRUE');
              }

              if(!$scope.editMode) {
                console.log('EditMode is now FALSE');
              }
            },
            mousemove: function(e) {
              if(!$scope.editMode) {
                chartPoint = null;
                return false;
              }

              // First point? Assign it and return
              if(null === chartPoint) {
                chartPoint = e;
                return false;
              }

              // Update inputs at the correct index will re-render graph and table data
              $scope.$apply(function() {
                var diffY = e.y - chartPoint.y; // if it is > 0 means that the mouse pointer went UP
                var time = diffY < 0 ? e.dataPoint.y+$scope.units : e.dataPoint.y-$scope.units;
                $scope.inputs[e.dataPointIndex].time = time;
                // Update chartPoint
                chartPoint = e;
              });

            },
             type: 'column',
             dataPoints: []
          }
       ]
    });

    $scope.chart.render(); //render the chart for the first time

    $scope.changeChartType = function(chartType) {
      $scope.canEdit = 'column' === chartType;
      $scope.editMode = false; // disable EditMode whatever the type is
      $scope.chart.options.data[0].dataPoints = datapoints;
      $scope.chart.options.data[0].type = chartType;
        $scope.chart.render(); //re-render the chart to display the new layout
    };

    // DEEP watch for any changes in the array
    $scope.$watch('inputs', function() {
      datapoints = [];
      for(var i in $scope.inputs) {
        var e = $scope.inputs[i];
        // update the timestamp for the value
        var d = new Date(e.date);
        e.timestamp = d.valueOf() / 1000;
        datapoints.push({
          'label': e.url,
          x: new Date(e.date),
          y: e.time
        });
      }
      // Reassign data to chart
      $scope.chart.options.data[0].dataPoints = datapoints;
      $scope.chart.render();
    }, true);


    $scope.inputs.push({
      date: 'January 02 2014',
      url: 'http://uno.it',
      time: 111
    });

    $scope.inputs.push({
      date: 'January 12 2014',
      url: 'http://due.it',
      time: 292
    });

    $scope.inputs.push({
      date: 'January 22 2014',
      url: 'http://tre.it',
      time: 333
    });

  });

test/spec/controllers/main.js '使用严格';

describe('Controller: MainCtrl', function () {

  // load the controller's module
  beforeEach(module('ChartApp'));

  var MainCtrl,
    scope;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope) {
    scope = $rootScope.$new();
    MainCtrl = $controller('MainCtrl', {
      $scope: scope
    });
  }));

  it('should attach a list of inputs to the scope ', function () {
    expect(scope.inputs.length).toBe(3);
  });
});

Karma.conf.js 包含所有必需的文件,我想...

....
    files: [
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
      'app/bower_components/angular-animate/angular-animate.js',
      'app/bower_components/angular-cookies/angular-cookies.js',
      'app/bower_components/angular-resource/angular-resource.js',
      'app/bower_components/angular-route/angular-route.js',
      'app/bower_components/angular-sanitize/angular-sanitize.js',
      'app/bower_components/angular-touch/angular-touch.js',

      'app/bower_components/jquery/dist/jquery.js',
      'app/bower_components/angular-ui/build/angular-ui.js',
      'app/bower_components/jquery-ui/jquery-ui.js',
      'app/scripts/vendor/canvasjs-1.5.0-beta/canvasjs.min.js',

      'app/scripts/directives/directive.js',
      'app/scripts/**/*.js',
      'test/mock/**/*.js',
      'test/spec/**/*.js'
    ],
...

-------------------- 编辑 #2 --------

这就是我在收到建议后编辑 main.js 测试的方式

  beforeEach(inject(function ($controller, $rootScope, $compile) {
    var htmlString = '' +
    '<div ng-controller="MainCtrl">' +
    '    <div id="chartContainer">' +
    '</div>'
    ;
    var element = angular.element(htmlString)
    scope = $rootScope.$new();
    $compile(element)(scope);
    scope.$digest();
    MainCtrl = $controller('MainCtrl', {
      $scope: scope
    });
  }));

错误几乎一样

LOG: 'CanvasJS Error: Chart Container with id "chartContainer" was not found'
PhantomJS 1.9.7 (Windows 7) Controller: MainCtrl should attach a list of inputs to the scope  FAILED
        TypeError: 'undefined' is not an object (evaluating 'this._toolBar.appendChild')
            at /path/to/workspace/Chart/app/scripts/vendor/canvasjs-1.5.0-beta/source/canvasjs.js:1799
            at /path/to/workspace/Chart/app/scripts/vendor/canvasjs-1.5.0-beta/source/canvasjs.js:1919
            at /path/to/workspace/Chart/app/scripts/vendor/canvasjs-1.5.0-beta/source/canvasjs.js:2093
            at /path/to/workspace/Chart/app/scripts/vendor/canvasjs-1.5.0-beta/source/canvasjs.js:12551
            at /path/to/workspace/Chart/app/scripts/controllers/main.js:101
            at invoke (/path/to/workspace/Chart/app/bower_components/angular/angular.js:3869)
            at instantiate (/path/to/workspace/Chart/app/bower_components/angular/angular.js:3880)
            at /path/to/workspace/Chart/app/bower_components/angular/angular.js:7134
            at /path/to/workspace/Chart/app/bower_components/angular/angular.js:6538
            at forEach (/path/to/workspace/Chart/app/bower_components/angular/angular.js:330)
            at nodeLinkFn (/path/to/workspace/Chart/app/bower_components/angular/angular.js:6552)
            at compositeLinkFn (/path/to/workspace/Chart/app/bower_components/angular/angular.js:5986)
            at publicLinkFn (/path/to/workspace/Chart/app/bower_components/angular/angular.js:5891)
            at /path/to/workspace/Chart/test/spec/controllers/main.js:20
            at invoke (/path/to/workspace/Chart/app/bower_components/angular/angular.js:3869)
            at workFn (/path/to/workspace/Chart/app/bower_components/angular-mocks/angular-mocks.js:2147)
        undefined
        TypeError: 'undefined' is not an object (evaluating 'scope.inputs.length')
            at /path/to/workspace/Chart/test/spec/controllers/main.js:28
PhantomJS 1.9.7 (Windows 7): Executed 1 of 1 (1 FAILED) ERROR (0.029 secs / 0.028 secs)
Warning: Task "karma:unit" failed. Use --force to continue.

我在更新测试控制器时是否弄错了什么?

-------------------- 编辑 #3 --------

在 Ivarni 的宝贵帮助下,我得到了这个解决方案:

在应用程序控制器中

...
$scope.chart = new $window.CanvasJS.Chart('chartContainer', {
...

在控制器测试中

...
beforeEach(inject(function ($controller, $rootScope, $compile, $window) {
    $window.CanvasJS = { Chart: function(){
        this.render = function() {};
    } };
...

CanvasJS 现在被模拟了,UnitTest 可以运行了 :)

【问题讨论】:

  • 嗨,我已经添加了整个主控制器,它的测试和 Karma 的配置

标签: angularjs unit-testing canvasjs


【解决方案1】:

我认为您对导致错误的原因的假设是正确的。您的控制器未附加到 DOM,因此它无法找到任何 id 为“chartContainer”的东西是合理的。

对于单元测试,我会尝试与单元测试指令相同的方法。 这类似于

beforeEach(inject(function($rootScope, $compile) {
    var htmlString = '' +
        '<div ng-controller="MainCtrl">' +
        '    <div id="chartContainer">' +
        '</div>'
        ;
    element = angular.element(htmlString)
    scope = $rootScope.$new();
    $compile(element)(scope);
    scope.$digest();
}));

这会编译一个最小的 DOM 片段,附加您的控制器和一个带有 CanvasJS 所需 id 的元素。你可能不得不更多地摆弄它。只要您确保将 CanvasJS 包含在 karma 配置中,我认为使用 CanvasJS 作为全局没有问题。

另一种探索途径是查看 Angular 的 e2e 测试功能。有一些文档here。这可能更适合您测试 DOM 渲染。

【讨论】:

  • 我试图对我的测试控制器进行一些编辑,但也许我错过了一些东西,因为错误几乎相同。我会更新我的问题以获得更好的代码格式。
  • Hm.. 如果在编译和消化 HTML 片段之前初始化控制器会发生什么?如果它仍然失败,那么我认为看 Protractor/e2e 是一种更可行的方法。
  • 相同:/。量角器和 e2e 测试已经在队列中,但是在进行 e2e 测试之前,我不应该对需要它的控制器部分进行单元测试吗?感谢您的帮助!
  • 我不认为 e2e 测试应该取代单元测试,但在某些情况下某些功能不适合单元测试。通过包含 CanvasJS,控制器还没有被完全隔离地测试。我并不是说在 jasmine 中运行它是不可能的,但我使用 $compile 的 goto 方法至少似乎不起作用。可能还有另一种方式。如果控制器中有其他与画布不直接相关的功能,那么提供 CanvasJS 的全局模拟实现仍然可以让您在单元测试中测试这些部分。
  • 当然。不要将 CanvasJS 构造函数作为全局(new CanvasJS.Chart(),顺便说一句等效于 new window.CanvasJS.Chart())访问,而是使用 Angular 的 $window 包装窗口的服务。现在您的代码变为new $window.CanvasJS.Chart()。现在您可以将 $window 的模拟传递给您的控制器,就像您已经将 $scope 传递给它一样。
猜你喜欢
  • 2014-09-21
  • 2014-04-06
  • 2016-08-24
  • 2013-07-11
  • 2012-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多