【问题标题】:Testing AngularJS controllers with resource services使用资源服务测试 AngularJS 控制器
【发布时间】:2013-06-02 05:34:03
【问题描述】:

背景: 我正在为 angular js 控制器编写单元测试,它利用包装在服务中的 angular $resources(出于可维护性目的)。

控制器示例:

name = 'app.controllers.UsersIndexCtrl'
angular.module(name, [])
.controller(name, [
  '$scope'
  '$location'
  '$dialog'
  'Users'
  'UserRoles'
  ($scope, $location, $dialog, Users, UserRoles) ->
    # Get users list
    $scope.users  = Users.query()

    # rest...

])

资源服务示例:

angular.module('app.services.Users', [])
.factory 'Users', ['$rootScope', '$http', '$resource', '$location' , ($rootScope, $http, $resource, $location)->
  baseUrl = '/users'
  Users = $resource baseUrl + '/:userId', {userId: '@_id'}

  Users.getStatus = ->
    console.log 'User::getStatus()'
    req = $http.get baseUrl + '/status'
    req.success (res)-> 
      $rootScope.globalUserAccountSettings = res
      unless $rootScope.$$phase then $rootScope.$apply()

  # other, custom methods go here...
])

Angular 中的大多数单元测试示例都建议使用 $httpBackend,从而在控制器中模拟 $http 服务。老实说,我怀疑这是否是一种好习惯,因为如果这样做,我必须在所有控制器测试中对请求路径进行硬编码,并且我想隔离单元行为。 $httpBackend mock 确实很棒,但前提是您直接在控制器中使用 $resource。

使用 $httpBackend 的典型单一测试如下所示:

it 'should be able to add a new empty user profile', ->
  $httpBackend.flush()
  l = $scope.users.length
  $httpBackend.expect('POST', '/users').respond _.cloneDeep mockResponseOK[0]
  $scope.add()
  $httpBackend.flush()
  expect($scope.users.length).toEqual l + 1

如果我创建了一个模拟用户资源类实例,例如:

angular.module('app.services.Users', [])
.factory 'Users', ->
  class Users
    $save:(cb)->
    $remove:->
    @query:->
    @get:->

Angular DI 机制将以透明的方式用这个覆盖旧的 'app.services.Users' 模块,并使我能够对 jasmine 间谍进行检查。

困扰我的是我无法找到一个单个示例来支持我的想法。所以问题是,你会使用哪一个?为什么或我做错了什么?

【问题讨论】:

    标签: unit-testing angularjs coffeescript jasmine karma-runner


    【解决方案1】:

    我是这方面的新手。我一直在使用 dsl 使用咖啡脚本编写测试,但今天我遇到了类似的问题。我解决它的方法是为我的资源创建一个茉莉花间谍。然后我创造了一个承诺。当 promise 被解决时,它将调用你在控制器中传入的“成功”函数。然后在'it'方法中,我实际上解决了承诺。

    我认为使用 js 和 jasmine 的代码看起来像这样,但我实际上没有时间检查

    beforeEach(inject(function($rootScope, $controller, $q ) {
      scope = $rootScope.$new();
      queryPromise = $q.defer()
      User = jasmine.createSpyObject("UsersStub", ["query"]);
      User.query.andCallFake(function(success,errror){queryPromise.promise.then(success,error)});
      HomeCtrl = $controller('HomeCtrl', {$scope: scope, Users: new Users()});
    }));
    
    it('has users in scope', function() {
      queryPrmomise.resolve({User1: {name"joe"})
        expect(scope.users).toEqual(queryResponse);
    });
    

    【讨论】:

      【解决方案2】:

      你能做的最好的事情是用应该被调用的方法制作一个假资源:

      var queryResponse = ['mary', 'joseph'],
          Users = function() {
          this.query = function() {
            return queryResponse;
          },
          scope, HomeCtrl;
      };
      
      beforeEach(inject(function($rootScope, $controller) {
        scope = $rootScope.$new();
        HomeCtrl = $controller('HomeCtrl', {$scope: scope, Users: new Users()});
      }));
      
      it('has users in scope', function() {
          expect(scope.users).toEqual(queryResponse);
      });
      

      【讨论】:

        【解决方案3】:

        我认为按照您的建议,在服务级别使用 Jasmine 间谍来存根更有意义。此时您是在对控制器进行单元测试,而不是对服务进行单元测试——发出 http 请求的确切方式不应成为此测试的关注点。

        你可以在你的规范中做这样的事情:

        var Users = jasmine.createSpyObj('UsersStub', ['query', 'get']);
        beforeEach(inject(function($provide) {
          $provide.factory('Users', function(){
            return Users;
          });
        });
        

        然后在您的相关测试中,您可以对各个服务方法进行存根,以在您的间谍对象上使用诸如“andCallFake”之类的方法返回您期望的内容。

        【讨论】:

        • beforeEach(module(function($provide) { ... 而不是注入?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-22
        • 1970-01-01
        • 2012-05-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多