AngularJS:如何测试带有resolve的promise逻辑?

10 浏览
0 Comments

AngularJS:如何测试带有resolve的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');
     // 如何在这里“刷新”defer,就像我刷新了httpBackend一样?
     expect( $scope.currentMonkeyHam.id ).toEqual('12345'); // 失败,因为它还没有定义,因为$q从未解析过

其中$scope只是我的控制器的作用域,但为了简洁起见,在这里称之为$scope。

更新:

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

model._monkeyHams = {} // 我们的缓存
model.fetchMonkeyHamById = function(id){
 return model.monkeyHams[id] || // 从缓存中获取或者
        (model.monkeyHams[id] = this.service.getById(id).then(function(result){
            return result.data;        
        }));
};

以下要求您已经与服务器进行过交互。我在前端创建一个模型(currentMonkeyHam)或其他任何东西,并且在第一次POST之后不再加载它(不必要的GET请求)。我只是使用当前模型。所以这不起作用,它要求至少从服务器上获取一次。因此,您可以看到为什么我创建了自己的deferred。我希望同时使用当前模型数据或者如果我们没有它,就从服务器获取。我需要这两种方法都返回一个promise。

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

0
0 Comments

AngularJS中如何测试带有解析逻辑的Promise?

在我们的代码库中,我们做了类似的事情,但是我们选择了更像传统仓库的方式,而不是使用一个不断变化的状态对象。

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
   }
}

返回一个已完成的deferred没有任何问题。这正是deferred的目的所在,它们负责处理何时以及如何解析它们。

至于你的测试,我们做了类似的事情。

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

这就是我对Promise行为的理解,你可以随时解析它们。我们的设置是一样的,只是我们称之为数据,而不是缓存。我无法让`then()`方法真正触发。我尝试了其他答案中建议的`$timeout`,然后调用了`$timeout.flush()`,它完美地工作了。请解释一下如何让`then()`触发。

我不能直接调用它,因为我要确保控制器正确使用它。

SomeCtroller.getData(){
   someRepository.someGetter.then(function(someData){ //console.log('i was called')});
}

在测试中,这段代码不会被调用,但在生产环境中却可以正常工作。

0
0 Comments

问题的原因是在测试环境中,resolve方法没有被调用,即then()方法没有执行。解决方法是使用$timeout.flush()来强制异步执行。

具体来说,问题的关键是在单元测试中,虽然resolve方法被调用了,但then()方法却没有执行。这可能是因为在测试环境中,promise对象的resolve方法没有立即执行,而是需要等待一段时间。

为了解决这个问题,可以使用$timeout.flush()方法来强制异步执行。具体操作如下:

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

通过调用$rootScope.digest()方法,可以立即执行resolve方法,从而使then()方法得到执行。

总之,通过使用$timeout.flush()方法,可以解决在测试环境中resolve方法没有被调用的问题。

0
0 Comments

问题的原因是代码中存在deferred反模式,这使得代码变得复杂,特别是因为它隐式地抑制错误。此外,对于缓存逻辑来说也是有问题的,因为如果在接收到响应之前进行了多个请求,可能会导致多次请求。

解决方法是简单地将promise缓存起来。在这种情况下,可以将promise缓存在model._monkeyHams对象中。如果有缓存,则直接返回缓存中的数据,否则通过service.getById(id)方法获取数据,并将promise缓存起来。

另一种通用的promise缓存模式是使用一个变量来存储缓存,如果没有缓存,则调用实际的请求方法来获取数据。

在测试方面,如果将model._monkeyHams[id]设置为一个特定的值,则会返回该值,而不会发送请求,可以通过这种方式来测试逻辑。

在讨论中提到了使用HTTP缓存的可能性,但是指出了promise缓存也是可以的,只需要添加一两行额外的代码。

最后,根据讨论中的更新,可以使用cache = $q.when(value)来直接赋值缓存,然后返回缓存值或者进行实际请求。

0