带服务注入的测试控制器

时间:2015-09-24 16:21:06

标签: angularjs testing constructor controller factory

我正在测试我的AngularJS应用程序,我正在努力进行注入服务的控制器的测试。

这是我的控制器:

.controller('UserCtrl', ['$scope', '$routeParams', 'User', function ($scope, $routeParams, User) {

    $scope.hello = "lol";

    $scope.userId = $routeParams.id;
    var user = new User($scope.userId);

    $scope.load = function(){
        user.load().then(
            function(user){
                $scope.name     = user.name;
            },
            function() {
                $scope.dataFail = 'error error' ;
            }
        );
    };

    $scope.load();
}]);

这是我的用户服务(我想在我的控制器测试中注入)

.factory('user', ['$http', '$q', function ($http, $q) {

var User = function (id) {
    this.id = id;
    this.name = null;
};

User.prototype.load = function () {
    var deferred = $q.defer();

    $http.post('/user/' + this.id + '/load')
        .success(function(data) {
            var user = new User(data.id);
            user.name = data.name
            deferred.resolve(user);
        })
        .error(function(err, code) {
            deferred.reject(err);
        $log.error(err, code);
    });
    return deferred.promise;
};

return User;

最后是我的考试:

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

var $controller, $scope, User;

beforeEach(module('octosendApp', function($provide){
    User = jasmine.createSpyObj('User', ['load']);

    User.load.and.returnValue({
        name: 'testName'
    });
    $provide.value('User', User);
}));

beforeEach(inject(function(_$controller_, $rootScope, _User_){
    $scope = $rootScope.$new();
    User = _User_;

    $controller = _$controller_('UserCtrl', {
        $scope: $scope,
        User : User,
        $routeParams: {id: 2}
    });
}));

it('should have a hello property', function() {
    expect($scope.hello).toBeDefined();
});

});

我收到以下错误:

TypeError: '[object Object]' is not a constructor (evaluating 'new User($scope.userId)')

我想问题是因为我的工厂是面向对象的,我不能使用构造函数。我跟着这个tutoarial:https://vimeo.com/90938739并做了完全相同的,唯一的区别是工厂的结构,我不想改变。

感谢您的帮助。

3 个答案:

答案 0 :(得分:0)

问题出在这一行,因为 User 应该是构造函数而不是object。

User = jasmine.createSpyObj('User', ['load']);

以下是如何对构造函数进行模拟。

// mock constructor
var UserMock = function () {
  // do some mocking..
}
User = jasmine.createSpy('User').and.callFake(UserMock);
$provide.value('User', User);

答案 1 :(得分:0)

我不确定使用spyOn是否对测试用例有用。

我会像S.Klechkovski一样创建一个模拟构造函数,但我会将它注入控制器中,如下所示:

mockUser = function () {};
mockUser.prototype.load = function () {//do your mocking promise};
ctrl = $controller('UserCtrl', { User: mockUser });

您的应用程序将使用mockUser作为用户服务,并且当您调用方法加载时,它将是模拟的。

答案 2 :(得分:0)

如果您只对代码进行了这些更改,则可以正常运行:

注释掉这些内容:

// User = jasmine.createSpyObj('User', ['load']);

// User.load.and.returnValue({
//   name: 'testName'
// });

然后添加以下行:

User = function(id) {  // as the other answers suggested, it is better to use a manually created fake than use a jasmine spy
  return {
    load: function() {
      return {
        then: function() {  // since the load function in the real `User` returns a promise, we need this
          return 'testUser ' + id;
        }
      };
    }
  };
};



// CODE

angular.module('octosendApp', [])
  .factory('user', ['$http', '$q',
    function($http, $q) {

      var User = function(id) {
        this.id = id;
        this.name = null;
      };

      User.prototype.load = function() {
        var deferred = $q.defer();

        $http.post('/user/' + this.id + '/load')
          .success(function(data) {
            var user = new User(data.id);
            user.name = data.name
            deferred.resolve(user);
          })
          .error(function(err, code) {
            deferred.reject(err);
            $log.error(err, code);
          });
        return deferred.promise;
      };

      return User;
    }
  ])
  .controller('UserCtrl', ['$scope', '$routeParams', 'User',
    function($scope, $routeParams, User) {

      $scope.hello = "lol";

      $scope.userId = $routeParams.id;
      var user = new User($scope.userId);

      $scope.load = function() {
        user.load().then(
          function(user) {
            $scope.name = user.name;
          },
          function() {
            $scope.dataFail = 'error error';
          }
        );
      };

      $scope.load();
    }
  ]);



// SPECS

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

  var $controller, $scope, User;

  beforeEach(module('octosendApp', function($provide) {
    // User = jasmine.createSpyObj('User', ['load']);

    // User.load.and.returnValue({
    //   name: 'testName'
    // });

    User = function(id) {
      return {
        load: function() {
          return {
            then: function() {
              return 'testUser ' + id;
            }
          };
        }
      };
    };

    $provide.value('User', User);
  }));

  beforeEach(inject(function(_$controller_, $rootScope, _User_) {
    $scope = $rootScope.$new();
    User = _User_;

    $controller = _$controller_('UserCtrl', {
      $scope: $scope,
      User: User,
      $routeParams: {
        id: 2
      }
    });
  }));

  it('should have a hello property', function() {
    expect($scope.hello).toBeDefined();
  });
});

// Runner

(function() {
  var jasmineEnv = jasmine.getEnv();
  jasmineEnv.updateInterval = 1000;

  var htmlReporter = new jasmine.HtmlReporter();

  jasmineEnv.addReporter(htmlReporter);

  jasmineEnv.specFilter = function(spec) {
    return htmlReporter.specFilter(spec);
  };

  var currentWindowOnload = window.onload;

  window.onload = function() {
    if (currentWindowOnload) {
      currentWindowOnload();
    }
    execJasmine();
  };

  function execJasmine() {
    jasmineEnv.execute();
  }

})();

<link href="http://jasmine.github.io/2.3/lib/jasmine.css" rel="stylesheet" />
<script src="http://jasmine.github.io/2.3/lib/jasmine.js"></script>
<script src="http://jasmine.github.io/2.3/lib/jasmine-html.js"></script>
<script src="http://jasmine.github.io/2.3/lib/boot.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="http://code.angularjs.org/1.2.9/angular-mocks.js"></script>
&#13;
&#13;
&#13;

我还在答案中嵌入了一个实时测试运动员片段,这要归功于我通过Google搜索找到的this fiddle。所以你可以运行它并看到它自己。

编辑:如果我们需要在控制器中添加更多测试用例,那么这似乎是模拟User服务的正确方法(正如我已尝试here ):

    User = function (id) {
        return {
            load: function () {
                return {
                    then: function (success, failure) {
                        success({
                            name: 'testUser ' + id
                        });
                    }
                };
            }
        };
    };