【问题标题】:Test promise in a controller在控制器中测试承诺
【发布时间】:2017-08-30 10:35:51
【问题描述】:

AngularJS 1.4.8

加载视图时,我在控制器中执行了以下方法。 Watcher 是注入控制器的工厂。方法.list()returns bluebird promise。

Watcher.list()
  .then((response) => {
    $scope.watchers = response;
  })
  .catch(notify.error);

我想像这样测试$scope.watchers

it('watchers have been loaded', function () {
  expect($scope.watchers.length).to.equal(2);
});

但收到以下错误:

Chrome 59.0.3071 (Linux 0.0.0) theApp watchersController watchers have been loaded FAILED
        Error: expected 0 to equal 2

目前,sinon stub 已尝试模拟 Wacther.list() 响应,请查看下面的完整测试文件。

import moment from 'moment';
import sinon from 'auto-release-sinon';
import Promise from 'bluebird';
import ngMock from 'ng_mock';
import expect from 'expect.js';
import _ from 'lodash';

import '../watchersController';

describe('watchersController', function () {
  let $httpBackend;
  let $scope;
  let $route;
  let Watcher;

  const init = function () {
    ngMock.inject(function ($rootScope, $controller, _$httpBackend_, _$route_, _Watcher_) {
      $scope = $rootScope;
      $route = _$route_;
      $httpBackend = _$httpBackend_;
      Watcher = _Watcher_;

      sinon.stub(Watcher, 'list', () => {
        return Promise.resolve([
          { id: '123' },
          { id: '456' }
        ]);
      });

      $route.current = {
        locals: {
          currentTime: moment('2016-08-08T11:56:42.108Z')
        }
      };

      $controller('WatchersController', {
        $scope,
        $route,
        $uibModal: {}
      });

      $scope.$apply();

    });
  };

  beforeEach(function () {
    init();
  });

  afterEach(function () {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });

  it('watchers have been loaded', function () {
    expect($scope.watchers.length).to.equal(2);
  });

});

【问题讨论】:

    标签: javascript angularjs sinon


    【解决方案1】:

    promise 解决后,您需要致电$scope.$apply。像这样的东西应该可以解决问题

    import moment from 'moment';
    import sinon from 'auto-release-sinon';
    import ngMock from 'ng_mock';
    import expect from 'expect.js';
    import _ from 'lodash';
    
    import '../watchersController';
    
    describe('watchersController', function () {
      let $httpBackend;
      let $scope;
      let $route;
      let Watcher;
      let deferred;
    
      const init = function () {
        ngMock.inject(function ($rootScope, $controller, _$httpBackend_, _$route_, _Watcher_, _$q_) {
          $scope = $rootScope;
          $route = _$route_;
          $httpBackend = _$httpBackend_;
          Watcher = _Watcher_;
          deferred = _$q_.defer(); // create a deferred
    
          // let stub return a promise to be resolved later
          sinon.stub(Watcher, 'list', () => deferred.promise);
    
          $route.current = {
            locals: {
              currentTime: moment('2016-08-08T11:56:42.108Z')
            }
          };
    
          $controller('WatchersController', {
            $scope,
            $route,
            $uibModal: {}
          });
    
        });
      };
    
      beforeEach(function () {
        init();
      });
    
      afterEach(function () {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
      });
    
      it('watchers have been loaded', function () {
        deferred.resolve([
              { id: '123' },
              { id: '456' }
        ]) // resolve deferred with mock data
    
        $scope.$apply(); // apply changes
        expect($scope.watchers.length).to.equal(2);
      });
    
    });
    

    您可能还想通过在另一个测试中调用 deferred.reject 来检查控制器是否处理承诺拒绝。

    【讨论】:

    • 测试不等待Watcher.list() promise 解决。测试和.list() 方法并行执行。因此,在测试执行期间,$scope.watchers 数组中仍有 0 个对象,结果为 Error: expected 0 to equal 2
    • 并行?你的意思是同时? deferred.resolve 是同步的,所以在你调用 $apply 之后控制器代码应该执行。你可以读到这种方法说here(它使用茉莉花,但主要思想是一样的)。
    • 是的,对不起,我的意思是同时。无论如何,我尝试了您的解决方案并得到了上述错误。
    • @trex " 我尝试了您的解决方案并得到了上述错误。"这很奇怪。确保您使用的是这个存根 () => deferred.promise 而不是原来的存根。
    【解决方案2】:

    您正在阅读该承诺之前的列表。 其实你的流程是这样的:

    • 使用 promise 导入类
    • 读取变量(尚未填充)
    • 测试它的价值

    请看下面的例子:

    function MockedClass() {
    	
      var _list = Promise.resolve([
              { id: '123' },
              { id: '456' }
            ]);
            
      var _watchersList = null;
      var _self = this;
      
    	this.fillList = function() {
      	_list.then(function(response){
          _self.watchersList = response;
        });
      }
      
      this.getList = function() {
      	return self._watchersList;
      }
    }
    
    (function testPromise() {
        console.log("Start");
        
        var p1 = Promise.resolve([
              { id: '123' },
              { id: '456' }
            ]);
        
        var mockedClass = new MockedClass();
        console.log("The list isn't ready: ", mockedClass.getList());
        mockedClass.fillList();
        console.log("The list isn't yet ready: ", mockedClass.getList());
        
        console.log("End");
    })();

    在上面的例子中,我们应该等待从getList()方法返回的值。

    因此,为了实现这一点,我们可以创建一个函数来尝试读取从getList() 方法返回的值,但它仍然可以进行最多十次尝试。

    请参阅以下工作示例:

    var attempts = 0;
    
    function MockedClass() {
    	
      var _list = Promise.resolve([
              { id: '123' },
              { id: '456' }
            ]);
            
      var _watchersList = null;
      var _self = this;
      
    	this.fillList = function() {
      	_list.then(function(response){
          _self.watchersList = response;
        });
      }
      
      this.getList = function() {
      	return _self.watchersList;
      }
    }
    
    (function testPromise() {
        console.log("Start");
        
        var p1 = Promise.resolve([
              { id: '123' },
              { id: '456' }
            ]);
        
        var mockedClass = new MockedClass();
        console.log("The list isn't ready: ", mockedClass.getList());
        mockedClass.fillList();
        
        onListReady(mockedClass, function(){
        	console.log("The list is ready: ", mockedClass.getList());
        });
        
        console.log("End");
    })();
    
    function onListReady(mockedClass, callback) {
    		attempts++;
        if(mockedClass.getList()===undefined && attempts<=10) {
        		console.log("waiting... attempt: " + attempts);
            setTimeout(function(){onListReady(mockedClass, callback)}, 50);
        } else {
        	callback();
        }
    }

    希望对你有帮助,再见。

    【讨论】:

      【解决方案3】:

      我们可以通过在Watcher.list()promise之后将测试函数推送到浏览器回调队列中来延迟测试函数的执行,例如使用setTimeout

        it('watchers have been loaded', function (done) {
          setTimeout(function () { // catch promise response
            expect($scope.watchers.length).to.equal(2);
            done();
          }); 
        }); 
      

      现在测试在回调队列返回Watcher.list() promise 之后执行。

      setTimeout 工作示例:

      console.log('Hi');
      setTimeout(function cb1() { 
        console.log('cb1');
      }, 5000);
      console.log('Bye');
      

      Read more about it.

      【讨论】:

        猜你喜欢
        • 2014-06-04
        • 1970-01-01
        • 2015-03-15
        • 2016-07-25
        • 2016-07-15
        • 1970-01-01
        • 1970-01-01
        • 2013-12-15
        • 1970-01-01
        相关资源
        最近更新 更多