你如何模拟一个 angularjs $resource 工厂

How do you mock an angularjs $resource factory

我有一个资源工厂

angular.module('mean.clusters').factory('Clusters', ['$resource',
  function($resource) {
    return $resource('clusters/:clusterId/:action', {
        clusterId: '@_id'
    }, {
        update: {method: 'PUT'},
        status: {method: 'GET', params: {action:'status'}}
    });
}]);

和控制器

angular.module('mean.clusters').controller('ClustersController', ['$scope',
  '$location', 'Clusters',
    function ($scope, $location, Clusters) {
        $scope.create = function () {
            var cluster = new Clusters();

            cluster.$save(function (response) {
                $location.path('clusters/' + response._id);
            });
        };

        $scope.update = function () {
            var cluster = $scope.cluster;

            cluster.$update(function () {
                $location.path('clusters/' + cluster._id);
            });
        };


        $scope.find = function () {
            Clusters.query(function (clusters) {
                $scope.clusters = clusters;

            });
        };
}]);

我正在编写我的单元测试,我发现的每个示例都使用某种形式的 $httpBackend.expect 来模拟来自服务器的响应,我可以做到这一点。

我的问题是,在对我的控制器功能进行单元测试时,我想模拟 Clusters 对象。如果我正在使用 $httpBackend.expect,并且我在我的工厂中引入了一个错误,我控制器中的每个单元测试都会失败。

我想测试 $scope.create 只测试 $scope.create 而不是我的工厂代码。

我尝试在测试的 beforeEach(module('mean', function ($provide) { 部分添加提供程序,但我似乎做不对。

我也试过了

clusterSpy = function (properties){
    for(var k in properties)
        this[k]=properties[k];
};

clusterSpy.$save = jasmine.createSpy().and.callFake(function (cb) {
    cb({_id: '1'});
});

并在 before(inject 中设置 Clusters = clusterSpy; 但在创建函数中,间谍会丢失

Error: Expected a spy, but got Function.

我已经能够让间谍对象为 cluster.$update 类型调用工作,但随后它在 var cluster = new Clusters(); 处失败并出现 'not a function' 错误。

我可以创建一个适用于 var cluster = new Clusters(); 但对 cluster.$update 类型调用失败的函数。

我可能在这里混用了术语,但是,是否有适当的方法来模拟具有函数间谍的集群,或者是否有充分的理由只使用 $httpBackend.expect

看起来我已经接近几次了,但我想我现在想通了。

解决方案是上面的 'I also tried' 部分,但我没有从函数返回间谍对象。

这有效,它可以放在 beforeEach(module(beforeEach(inject 部分

第 1 步:使用您要测试的任何函数创建间谍对象,并将其分配给您的测试可以访问的变量。

第 2 步:创建一个 returns 间谍对象的函数。

第 3 步:将间谍对象的属性复制到新函数中。

clusterSpy = jasmine.createSpyObj('Clusters', ['$save', 'update', 'status']);

clusterSpyFunc = function () {
    return clusterSpy
};

for(var k in clusterSpy){
    clusterSpyFunc[k]=clusterSpy[k];
}

第 4 步:将其添加到 beforeEach(inject 部分的 $controller。

ClustersController = $controller('ClustersController', {
    $scope: scope,
    Clusters: clusterSpyFunc
});

在您的测试中,您仍然可以使用

向方法添加功能
clusterSpy.$save.and.callFake(function (cb) {
    cb({_id: '1'});
});

然后检查间谍值

expect(clusterSpy.$save).toHaveBeenCalled();

这解决了 new Clusters()Clusters.query 不是函数的问题。现在我可以在不依赖资源工厂的情况下对我的控制器进行单元测试。

另一种模拟集群服务的方法是:

describe('Cluster Controller', function() {

    var location, scope, controller, MockClusters, passPromise, q;
    var cluster = {_id : '1'};

    beforeEach(function(){

      // since we are outside of angular.js framework,
      // we inject the angujar.js services that we need later on
      inject(function($rootScope, $controller, $q) {
        scope = $rootScope.$new();
        controller = $controller;
        q = $q;
      });

      // let's mock the location service
      location = {path: jasmine.createSpy('path')};

      // let's mock the Clusters service
      var MockClusters = function(){};
      // since MockClusters is a function object (not literal object)
      // we'll need to use the "prototype" property
      // for adding methods to the object
      MockClusters.prototype.$save = function(success, error) {
        var deferred = q.defer();
        var promise = deferred.promise;

        // since the Clusters controller expect the result to be
        // sent back as a callback, we register the success and
        // error callbacks with the promise
        promise.then(success, error);

        // conditionally resolve the promise so we can test
        // both paths
        if(passPromise){
          deferred.resolve(cluster);
        } else {
          deferred.reject();
        }
      }

      // import the module containing the Clusters controller
      module('mean.clusters')
      // create an instance of the controller we unit test
      // using the services we mocked (except scope)
      controller('ClustersController', {
        $scope: scope,
        $location: location,
        Clusters: MockClusters
      });


    it('save completes successfully', function() {
      passPromise = true;
      scope.save();

      // since MockClusters.$save contains a promise (e.g. an async call)
      // we tell angular to process this async call before we can validate
      // the response
      scope.$apply();

      // we can call "toHaveBeenCalledWith" since we mocked "location.path" as a spy
      expect(location.path).toHaveBeenCalledWith('clusters/' + cluster._id););

    });

    it('save doesn''t complete successfully', function() {
      passPromise = false;
      scope.save();

      // since MockClusters.$save contains a promise (e.g. an async call)
      // we tell angular to process this async call before we can validate
      // the response
      scope.$apply();

      expect(location.path).toHaveBeenCalledWith('/error'););

    });

  });

});