【问题标题】:Unit Testing of chain of promises with Jasmine使用 Jasmine 对承诺链进行单元测试
【发布时间】:2018-06-02 11:21:31
【问题描述】:

我想为有很多承诺链的工厂编写单元测试。下面是我的代码sn-p:

angular.module('myServices',[])
.factory( "myService",
    ['$q','someOtherService1', 'someOtherService2', 'someOtherService3',  'someOtherService4',
    function($q, someOtherService1, someOtherService2,  someOtherService3,  someOtherService4) {

method1{
	method2().then(
		function(){	someOtherService3.method3();},
		function(error){/*log error;*/}
	);
	return true;
};

var method2 = function(){
	var defer = $q.defer();
	var chainPromise = null;
	angular.forEach(myObject,function(value, key){
		if(chainPromise){
			chainPromise = chainPromise.then(
				function(){return method4(key, value.data);},
				function(error){/*log error*/});
		}else{
			chainPromise = method4(key, value.data);
		}
	});

	chainPromise.then(
		function(){defer.resolve();},
		function(error){defer.reject(error);}
	);
	return defer.promise;
};

function method4(arg1, arg2){
	var defer = $q.defer();
	someOtherService4.method5(
		function(data) {defer.resolve();},
		function(error) {defer.reject(error);},
		[arg1,arg2]
	);
	return defer.promise;
};

var method6 = function(){
	method1();
};
return{
method6:method6,
method4:method4
};
}]);

为了测试它,我为所有服务创建了 spy 对象,但提到了有问题的一个

beforeEach( function() {
    someOtherService4Spy = jasmine.createSpyObj('someOtherService4', ['method4']); 
    someOtherService4Spy.method4.andCallFake(
        function(successCallback, errorCallback, data) {
           // var deferred = $q.defer();
            var error = function (errorCallback) { return error;}
            var success = function (successCallback) {
                deferred.resolve();
                return success;
            }
            return { success: success, error: error};
        }
    );

    module(function($provide) {
        $provide.value('someOtherService4', someOtherService4);
    });
    inject( function(_myService_, $injector, _$rootScope_,_$q_){
        myService = _myService_;
        $q = _$q_;
        $rootScope = _$rootScope_;
        deferred = _$q_.defer();
    });

});

it("test method6", function() {
    myService.method6();
    var expected  = expected;
    $rootScope.$digest();

    expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
    expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
    expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
});

显示错误

expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);

调试后发现它并没有等待任何promise解决,所以方法1返回true,甚至没有执行方法3。我什至尝试过

someOtherService4Spy.method4.andReturn(function(){return deferred.promise;});

但结果保持不变。 我的问题是我是否需要多次解决,即每个承诺。我怎样才能等到所有的承诺都被执行。

【问题讨论】:

    标签: javascript angularjs unit-testing karma-jasmine angular-promise


    【解决方案1】:

    我做了以下修改以使其正常工作。我错过了对 method5 请求的处理,因为它处于挂起状态。一旦我处理了对方法 5 的所有请求并提供了 successCallback(以及对 digest() 的调用),它就开始工作了。

        someOtherService4Spy.responseArray = {};
        someOtherService4Spy.requests = [];
        someOtherService4Spy.Method4.andCallFake( function(successCallback, errorCallback, data){
    
            var request = {data:data, successCallback: successCallback, errorCallback: errorCallback};
            someOtherService4Spy.requests.push(request);
    
            var error = function(errorCallback) {
                request.errorCallback = errorCallback;
            }
            var success = function(successCallback) {
                request.successCallback = successCallback;
                return {error: error};
            }
    
            return { success: success, error: error};
        });
    
        someOtherService4Spy.flush = function() {
            while(someOtherService4Spy.requests.length > 0) {
                var cachedRequests = someOtherService4Spy.requests;
                someOtherService4Spy.requests = [];
                cachedRequests.forEach(function (request) {
                    if (someOtherService4Spy.responseArray[request.data[1]]) {
                        request.successCallback(someOtherService4Spy.responseArray[request.data[1]]);
                    } else {
                        request.errorCallback(undefined);
                    }
                    $rootScope.$digest();
                });
            }
        }
    

    然后我将我的测试修改为:

        it("test method6", function() {
        myService.method6();
    
        var expected  = expected;
        var dataDict = {data1:"data1", data2:"data2"};
        for (var data in dataDict) {
            if (dataDict.hasOwnProperty(data)) {
                someOtherService4Spy.responseArray[dataDict[data]] = dataDict[data];
            }
        }
    
        someOtherService4Spy.flush();
    
        expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
        expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
    
    });
    

    这符合我的预期。由于承诺链,我正在考虑这个问题,但是当我处理 method5 回调方法时,它得到了解决。我想到了刷新请求的想法,就像我为 http 调用做的事情一样。

    【讨论】:

      【解决方案2】:

      method1 不返回承诺,所以你怎么知道它调用的异步函数已经完成。相反,您应该返回:

      return method2().then(
      

      method6 调用异步函数但又不返回一个承诺(它返回未定义)所以你怎么知道它完成了?你应该返回:

      return method1();
      

      在测试中,你应该 mock $q 并让它解析或拒绝一个值,但我想不出为什么你会有一个不返回任何东西的异步函数,因为你不会知道它是否失败以及何时完成。

      方法 2 可以用更稳定的方式编写,因为如果神奇出现的 myObject 为空({}[]),它当前会崩溃。

      var method2 = function(){
        var defer = $q.defer();
        var keys = Object.keys(myObject);
        return keys.reduce(
          function(acc,item,index){
            return acc.then(
              function(){return method4(keys[index],myObject[key].data);},
              function(err){console.log("error calling method4:",err,key,myObject[key]);}
            )
          }
          ,$q.defer().resolve()
        )
      };
      

      并且尽量不要在你的函数中出现神奇的变量,这可能是一个全局变量,但你的代码没有显示它的来源,我怀疑有必要将它的范围限制在你的函数之外of 传递给函数。

      您可以了解更多关于 promises here 的信息,您应该了解为什么函数会返回 promise(函数不会阻塞)以及处理程序是如何放入队列的。这样以后可以省去很多麻烦。

      【讨论】:

      • $q 承诺应该被同步测试,没有done。它们严重依赖 $rootScope.$digest()。您发布的代码可能会导致测试超时。
      • @estus 感谢您提供的信息,OP 发布的代码似乎没有任何意义,如果您调用 method1 或 method6 则无法知道它们何时完成。也许在测试中你可以 mock $q 但在生产中它似乎没有任何用途。
      • 这只是一个虚拟代码,所以没有共享整个代码,因此您可以看到一些全局变量。我不希望 method1 和 method6 返回一个承诺,但我同意 HMR 可以优化方法 2,我已经做到了。
      猜你喜欢
      • 1970-01-01
      • 2013-12-23
      • 2016-10-25
      • 2020-07-30
      • 2016-09-26
      • 1970-01-01
      • 2016-10-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多