当使用 sinon 进行单元测试时,我怎么知道我的控制器被调用了?

How do I know my controller was called, when unit testing with sinon?

Angular 中的单元测试还很陌生。我已经阅读了大量关于间谍、存根和模拟的内容,但我在执行基础知识时遇到了很多麻烦:


Controller.spec(很确定需要以下内容)

    'use strict';
    describe('Controller: MainController', function() {

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

      var MainController, scope;

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

其余规格:

it('should have called initializePage', function() {
        var spyInstance = sinon.spy(MainController, "initializePage");
        assert(spyInstance.called, "initializePage() was not called once");
      });
    });

我一直认为间谍就足够了,但我不确定 MainController 是否被处决了。当前 spyInstance 抛出错误。 我需要在这里存根吗?为什么?

控制器

class MainController {
  constructor($scope, $http, $state, Session) {
    this.$scope = $scope;
    this.$state = $state;
    this.Session = Session;
    this.initializePage();
}

initializePage() {
//blah blah
}

谢谢。


修订: main.controller.spec.js

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

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

  var mainControllerInstance, scope;

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

  it('should test the controller is in order', function() {
    assert.isFunction(mainControllerInstance.$onInit, "$onInit() has not been defined");

    sinon.spy(mainControllerInstance, "$onInit");
    assert(mainControllerInstance.$onInit.called, "$onInit() called = false");
  });




});

控制器测试在 Angular 中有一些缺陷。主要是因为无法监视仅作为 class 实例可用的 class 的构造函数($controller(...) 的结果是)。当$controller(...)被调用时,没有什么可监视的,构造函数已经被调用,故事结束。

为此,除了 Angular 模块之外,还应使用 ES6/CommonJS 模块来公开控制器 class 并监视原型方法。由于项目中已经使用了ES6,所以是

export class MainController { ... }

import { MainController } from '...';
...
scope = $rootScope.$new();
sinon.spy(MainController.prototype, 'initializePage');
mainControllerInstance = $controller('MainController', { $scope: scope });
assert(MainController.prototype.initializePage.called);
assert.strictEqual(mainControllerInstance.$scope, $scope);
...

但更重要的是,initializePage 重新发明了轮子。它的工作已经由 Angular 1.5 及更高版本中的 $onInit 生命周期挂钩处理。它会在指令编译时自动调用,并可以作为 link 前函数的替代。

$onInit 未在控制器实例化时调用,但可以安全地假设它将在指令中,因此无需监视它。更加测试友好,测试变成

class MainController {
  constructor($scope, $http, $state, Session) {
    this.$scope = $scope;
    this.$state = $state;
    this.Session = Session;
  }

  $onInit() {
    //blah blah
  }
}

scope = $rootScope.$new();
mainControllerInstance = $controller('MainController', { $scope: scope });
assert.isFunction(mainControllerInstance.$onInit);
assert.strictEqual(mainControllerInstance.$scope, $scope);
...