AngularJS:如何在解决方案中测试承诺逻辑?

时间:2014-09-04 17:48:14

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

我有一个具有一些缓存逻辑的服务方法:

model.fetchMonkeyHamById = function(id)
{
    var that = this;
    var deferred = $q.defer();
    if( that.data.monkeyHam )
    {      
         deferred.resolve( that.data.monkeyHam );
         return deferred.promise;
     } else {
          return this.service.getById( id).then( function(result){
             that.data.monkeyHam = result.data;           
         });
    }
};

我知道如何使用$ httpBackend强制返回模拟数据。现在,当我明确设置数据时,如何强制它解析(然后测试结果)?

我想在控制器中测试结果then()函数:

            MonkeyHamModel.fetchMonkeyHamById(this.monkeyHamId).then( function() {
                $scope.currentMonkeyHam = MonkeyHamModel.data.monkeyHam;
            });

然后我的测试我想显式设置数据(所以它从内存加载“缓存”而不是httpBackend)

     MonkeyHamModel.data.monkeyHam = {id:'12345'};
     MonkeyHamModel.fetchMonkeyHamById( '12345');
     // How to "flush" the defer right here like I would have flushed httpBackend?
     expect( $scope.currentMonkeyHam.id ).toEqual('12345'); //fails because it is not defined yet since the $q never resolved

其中$ scope只是我的控制器的范围,但为简洁起见,这里称为$ scope。

更新

建议的答案不起作用。我需要函数来返回一个promise,而不是一个promise的结果:

model._monkeyHams = {} // our cache
model.fetchMonkeyHamById = function(id){
 return model.monkeyHams[id] || // get from cache or
        (model.monkeyHams[id] = this.service.getById(id).then(function(result){
            return result.data;        
        }));
};

以下要求您已经触摸过服务器。我在前端创建了一个模型(currentMonkeyHam)或者其他什么,并且在第一次POST(不必要的GET请求)之后不要加载它。我只是使用当前的模型。所以这不起作用,它需要至少一次到服务器。因此,你可以看到为什么我创建了自己的延迟。我想使用当前的模型数据,或者如果我们没有它,则从服务器获取它。我需要两种途径来回报承诺。

var cache = null;
function cachedRequest(){
    return cache || (cache = actualRequest())
}

3 个答案:

答案 0 :(得分:3)

您的代码具有延迟反模式,这使得它变得复杂 - 特别是因为您隐式地使用它来抑制错误。此外,缓存逻辑也存在问题,因为如果在收到响应之前发出了多个请求,您最终可能会发出多个请求。

你过分暗示它 - 只是缓存承诺:

model._monkeyHams = {} // our cache
model.fetchMonkeyHamById = function(id){
 return model.monkeyHams[id] || // get from cache or
        (model.monkeyHams[id] = this.service.getById(id).then(function(result){
            return result.data;        
        }));
};

在你的情况下,你将所有ID缓存为同一个东西,缓存承诺的一般模式是这样的:

var cache = null;
function cachedRequest(){
    return cache || (cache = actualRequest())
}

创造延迟是乏味而坦率的 - 不是很有趣;)

答案 1 :(得分:1)

您可以使用setTimeout (or $timeout)来解决承诺。

您可以将代码修改为 -

model.fetchMonkeyHamById = function(id)
{
    var that = this;
    var deferred = $q.defer();
    if( that.data.monkeyHam )
    {      
         setTimeout(function() {
            deferred.resolve(that.data.monkeyHam);
         }, 100);
         return deferred.promise;
     } else {
          return this.service.getById( id).then( function(result){
             that.data.monkeyHam = result.data;           
         });
    }
};

修改

按照本杰明的建议修改 -

使用$rootScope.digest() - 代码应该是这样的

MonkeyHamModel.data.monkeyHam = {id:'12345'};
MonkeyHamModel.fetchMonkeyHamById( '12345');
$rootScope.digest();

答案 2 :(得分:0)

我们在代码库中做了类似的事情,但是我们没有使用状态不断改变的对象,而是使用了更像传统存储库的东西。

someInjectedRepoistory.getMonkeyHamModel().then(x => $scope.monkeyHam = x);

Repository{
   getMonkeyHamModel() {
       var deferred = $q.defer();
       if( this.cache.monkeyHam )
       {      
            deferred.resolve( this.cache.monkeyHam );
        } else {
             return this.service.getById( id).then( function(result){
                this.cache.monkeyHam  = result.data;           
            });
       }
       return deferred.promise
   }
}

返回完成的延期没有问题。这是延期目的的一部分,无论何时或如何解决它们都不重要,它们会为您处理所有这些。

至于你的测试,我们会做这样的事情。

testGetFromService(){
   someInjectedRepoistory.getMonkeyHamModel().then(x => verifyMonkeyHam(x));
   verifyHttpBackEndGetsCalled()
}

testGetFromCache(){
   someInjectedRepoistory.getMonkeyHamModel().then(x => verifyMonkeyHam(x));
   verifyHttpBackEndDoesNotGetCalled()
}