如何在 jasmine 中对具有外部依赖项的 ngcontroller 进行单元测试?

how to unit test a ngcontroller with external dependencies in jasmine?

我正在尝试为 angularcontroller 编写业力单元测试,这是控制器。从这里使用文件上传 (https://github.com/nervgh/angular-file-upload):

var c, di;

c = function($scope, FileUploader, UploadHelper, currentUser) {
  var uploader;

  $scope.uploader = uploader = new FileUploader(UploadHelper.getOptions());

  uploader.onSuccessItem = function(item, response, status, headers) {
    //do something
  };
  return this;
};

di = [ '$scope', 'FileUploader', 'UploadHelper', 'currentUser', c];

angular.module('mycontrollers').controller('DocumentsController', di);

我的茉莉花测试是这样的:

describe('Customer: Controller: DocumentsController', function() {
  var $rootScope, DocumentMock, UserMock, documentsController, scope;
  documentsController = $rootScope = scope = DocumentMock = UserMock = null;

  beforeEach(function() {
    module("mycontrollers");
    return inject(function($controller, _$rootScope_, $q) {
      var deferred, documents, fileUploader, uploadHelper, user;
      $rootScope = _$rootScope_;
      scope = $rootScope.$new();
      documents = [
        {
          id: "doc 1",
          documentType: "other"
        }, {
          id: "doc 2",
          documentType: "pdf"
        }, {
          id: "doc 3",
          v: "pdf"
        }
      ];
      user = {
        id: 'user_id',
        username: 'username'
      };
      UserMock = {
        currentUser: function() {
          return user;
        }
      };
      fileUploader = {};
      uploadHelper = {};

      return documentsController = $controller('DocumentsController', {
        documents: documents,
        $scope: scope,
        FileUploader: fileUploader,
        UploadHelper: uploadHelper,
        currentUser: UserMock

      });
    });
  });
  return describe("On creation", function() {
    return it("should assign documents to scope", function() {
      return expect(1).toEqual(1);
    });
  });
});

我有一个名为 QuestionsAttachments 的服务,它有一些依赖项:

s = function(API, $http, $q, $timeout) {
  var QuestionsAttachments;
  return QuestionsAttachments = (function() {
    function QuestionsAttachments(data) {
      angular.extend(this, data);
    }

    QuestionsAttachments["new"] = function(data) {
      return new QuestionsAttachments(data);
    };

    return QuestionsAttachments;

  })();
};

di = ['API', '$http', '$q', '$timeout', s];

这个问题是:如何模拟 API ,$http, $q, $timeout ?

这是一个 link 的 plunkr: http://plnkr.co/edit/BT8SJgW6ejcPw6xo1cpI?p=preview

Basically what you do is you set up a spy on the method of that service. All you have to do is get the injetable service (the real implementation) and the just mock/stub the function(s) you need. No need to create your own mock objects like so: uploadHelper = {}

spyOn(uploadHelper, 'getOptions').andCallFake(function(){
    // return data here
});

// or you could use .Return({ /* some object */})

You can read more about jasmine and spies in particular here

话虽如此,我必须说提供的代码看起来确实有点不完整。 例如,根据示例,如果您没有创建任何模块并且您直接使用 getter 语法而不是 angular.module('mycontrollers', []);.

其次,在您的测试中,您试图在控制器实例化期间注入 documents,但这将永远行不通,因为您没有可注入的参数 'documents'在您的控制器定义中。

所以您遇到的问题很可能是由于上面的概述。

无论如何,一个完整的例子,重点是如何 mock/stub/spy on UploadHelper.getOptions() 可能看起来像这样。这是一个可用的 Plunkr http://plnkr.co/edit/RfQFsaLq8XV2FvMT3xr0?p=preview

实施

angular
    .module('mycontrollers',[])
    .controller('DocumentsController', documentsController);
    
    documentsController.$inject = ['$scope', 'FileUploader', 'UploadHelper', 'currentUser'];
    
    function documentsController($scope, FileUploader, UploadHelper, currentUser){
        var uploader;
        
        $scope.uploader = uploader = new FileUploader(UploadHelper.getOptions());
        
        uploader.onSuccessItem = function(item, response, status, headers){
            // do something
        };
    }

测试

describe('Customer: Controller: DocumentsController', function(){
    beforeEach(function(){
        module('mycontrollers');
        
        inject(function($injector){
            // fetch our dependencies
            this.$controller = $injector.get('$controller');            
            this.FileUploader = $injector.get('FileUploader');
            this.UploadHelper = $injector.get('UploadHelper');
            this.$scope = $injector.get('$rootScope').$new();
            this.user = {
                id: 'user_id',
                username: 'username'
            };
            this.UserMock = {
                currentUser: function(){
                    return user;
                }
            }
        });
    });
    
    function initController(context){
        return context.$controller('DocumentsController', {
                $scope: context.$scope,
                FileUploader: context.FileUploader,
                UploadHelper: context.UploadHelper,
                currentUser: context.UserMock
            });
    }
    
    describe('On creation', function(){
        it('should call UploadHelper.getOptions()', function(){
            // spy on getOptions and when it is getting called, return whatever we've specified
            spyOn(this.UploadHelper, 'getOptions').andReturn({ /* some options */});
            
            // instantiate the controller
            initController(this);
            
            expect(this.UploadHelper.getOptions).toHaveBeenCalled();
            expect(this.$scope.uploader.onSuccessItem).toBeDefined();
        });
    });
});

这是一个为注入服务创建完整模拟的简短示例。

var mockInjectedProvider;

beforeEach(function() {
  module('myModule');
});

beforeEach(inject(function(_injected_) {
      mockInjectedProvider = mock(_injected_);
    });

    beforeEach(inject(function(_base_) {
      baseProvider = _base_;
    }));

    it("injectedProvider should be mocked", function() {
      mockInjectedProvider.myFunc.andReturn('testvalue');
      var resultFromMockedProvider = baseProvider.executeMyFuncFromInjected();
      expect(resultFromMockedProvider).toEqual('testvalue');
    });

    //mock all service methods
    function mock(angularServiceToMock) {

      for (var i = 0; i < Object.getOwnPropertyNames(angularServiceToMock).length; i++) {
        spyOn(angularServiceToMock, Object.getOwnPropertyNames(angularServiceToMock)[i]);
      }
      return angularServiceToMock;
    }