javascript - 我如何模拟 AngularJS 单元测试中 promise 的结果?

标签 javascript angularjs unit-testing promise

我的 CompanyService 是:

angular.module('mean').service('CompanyService', ['$http', '$rootScope', '$q', function($http, $rootScope, $q) {
  var company = this;
  var initializedDeferred = $q.defer();

  company.company_data = {}
  company.initialized = initializedDeferred.promise;

  company.getCompany = function() {
    return company.company_data;
  }

  company.get = function (company_id) {
    return $http({
      url: '/api/v1/company/' + company_id,
      method: 'GET',
      headers: {
        api_key: $rootScope.api_key
      }
    }).success(function(response) {
      if(response.status === 'ok') {
        company.company_data = response.company;
        initializedDeferred.resolve();
      } else {
        alert('TBD: Need fail case');
      }
    });
  };
}]);

我的 Controller 是:

angular.module('mean').controller('LocationController', ['$scope', '$location', '$rootScope', 'LocationService', 'UserService', 'CompanyService', '$modal', '$routeParams', function ($scope, $location, $rootScope, LocationService, UserService, CompanyService, $modal, $routeParams) {
  $rootScope.menuItem = 'locations';
  $scope.contentTemplate = '/views/location/index.html';
  $scope.locations = [];
  $scope.current_location = null;
  $scope.newLocation = {};
  $scope.location_parent_id = $routeParams.location_parent_id;

  $scope.index = function() {
    CompanyService.initialized.then(function() {
      $scope.test = 'a';
      var company_id = CompanyService.getCompany()._id;
      LocationService.list(company_id, $routeParams.location_parent_id).then(function(response) {
        if(response.data.status === 'ok') {
          $scope.locations = response.data.locations;
          $scope.current_location = response.data.location || null;
        }
      });
    });
  }

  $scope.addLocationModal = function() {
    $scope.location_types = ['warehouse', 'section', 'row', 'shelf', 'bin'];
    $modal({
      scope: $scope,
      template: '/views/location/addLocationModal.html',
      show: true,
      animation: 'am-fade-and-scale'
    });
  }

  $scope.createLocation = function() {
    $scope.newLocation.company_id = CompanyService.getCompany()._id;
    LocationService.create($scope.newLocation).then(function(response) {
      if(response.data.status === 'ok') {
        $scope.$hide();
      } else {
        alert('TBD');
      }
    });
  }

}]);

我的测试是:

(function() {
  describe('LocationController', function() {
    var $scope, $location, $rootScope, $modal, deferred, CompanyService, createController;

    beforeEach(module('mean'));

    beforeEach(inject(function($injector) {
      $location = $injector.get('$location');
      $rootScope = $injector.get('$rootScope');
      $modal = $injector.get('$modal');
      $scope = $rootScope.$new();

      var $controller = $injector.get('$controller');

      var $q = $injector.get('$q');

      var params = {
        '$scope': $scope,
        CompanyService: jasmine.createSpyObj('CompanyService', ['initialized'])
      }

      params.CompanyService.initialized.andCallFake(function () {
        deferred = $q.defer();
        return deferred.promise;
      });


      createController = function() {
        return $controller('LocationController', params);
      };
    }));

    it('should instantiate initial variables at the top level', function() {
      var controller = createController();

      $location.path('/company/locations');
      expect($location.path()).toBe('/company/locations');
      expect($rootScope.menuItem).toBe('locations');
      expect($scope.contentTemplate).toBe('/views/location/index.html');
      expect($scope.locations.length).toEqual(0);
      expect($scope.current_location).toBeNull();
      expect($scope.newLocation).toBeDefined();
      expect($scope.location_parent_id).not.toBeDefined();
    });

    it('should list all locations for this company', function() {
      var controller = createController();
      deferred.resolve();
      $scope.index();

      expect($scope.test).toBe('a');
    });
  });
})();

但是,resolve 不起作用。我收到此错误: TypeError: 'undefined' 不是一个对象(评估 'deferred.resolve')

有什么帮助吗?

最佳答案

在您的测试中,您使用伪造的 CompanyService.initialized 函数创建您的延迟对象。但是,只有在调用 $scope.index(); 时才会调用此函数,它在 deferred.resolve(); 行之后执行。以下应该有效:

it('should list all locations for this company', function() {
  var controller = createController();
  $scope.index();   // Should in turn call the fake CompanyService.initialized function that creates deferred
  deferred.resolve();
  $scope.$apply();  // Fire $digest cycle to dispatch promises.

  expect($scope.test).toBe('a');
});

更新

Jasmine 不支持监视非函数的属性。所以你的 spy 设置​​是无效的,因为 CompanyService.initialized 是一个对象而不是一个函数,所以你的 andCallFake 不会工作。解决方法是在 CompanyService 中引入一个 getter 函数,例如:

company.isInitialized(){ return company.initialized; }

然后在你的 Controller 中使用这个 getter 函数:

$scope.index = function() {
  CompanyService.isInitialized().then(function() {
    $scope.test = 'a';
    // Removed for brevity
  });
}

最后更新您的测试代码初始化:

var params = {
  '$scope': $scope,
  CompanyService: jasmine.createSpyObj('CompanyService', ['isInitialized'])
}
params.CompanyService.isInitialized.andCallFake(function () {
  deferred = $q.defer();
  return deferred.promise;
});

关于javascript - 我如何模拟 AngularJS 单元测试中 promise 的结果?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21948459/

相关文章:

javascript - 使用Sinon的Mongoose模型的 stub 保存实例方法

javascript - 如何从外部服务器加载特定数据?

javascript - 文档底部编写的代码总是在 DOM 准备好之后执行吗?

javascript - 如何比较对象内的值具有不同序列的数组javascript

angularjs - 如何处理 resolve : clause of $routeProvider 中的异常

ruby-on-rails - 在Rails中,为什么仅在测试中我的邮件正文为空?

javascript - 在内存中查找实例

javascript - 使用 org-mode 在 emacs 中管理 firefox 选项卡

angularjs - ng-bind 与 angular 中的一次绑定(bind)有什么区别

java - 使用 PIT 查找无用的单元测试