【问题标题】:AngularJS : How to test promise logic with a resolve in it?AngularJS:如何用 resolve 来测试 Promise 逻辑?
【发布时间】:2015-09-29 21:05:09
【问题描述】:

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

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。

更新:

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

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 请求)之后不加载它。我只是使用当前的模型。所以这是行不通的,它需要至少去服务器一次。因此,您可以看到为什么我创建了自己的 deferred。如果我们没有,我想使用当前模型数据或从服务器获取它。我需要两种途径来回报承诺。

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

【问题讨论】:

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


    【解决方案1】:

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

    你想多了 - 只是缓存承诺:

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

    坦率地说,创建 deferred 既乏味又乏味 - 不是很有趣 ;)

    【讨论】:

    • 关于测试它的主题 - 如果您将任何特定 ID 的 model._monkeyHams[id] 设置为一个值,它将返回该值而不是发出该请求,您可以测试该逻辑
    • “您将所有 ID 缓存为同一事物”在这种特殊情况下我们只有一个。其他人使用列表/获取器/设置器。无论如何,我应该只使用 HTTP 缓存吗? stackoverflow.com/questions/14117653/…
    • @FlavorScape 不,承诺缓存很好 - 正如您所见,它只是一到两行额外的代码。这取决于。
    • 酷我猜一开始我试图弄清楚如何返回静态数据或承诺的结果——我发现的例子都是反模式的味道。这样更有意义。
    • @FlavorScape 人们在开始使用 Promise 时会遇到困难,我知道我曾经这样做过 - 您可以在此处看到该模式的 self contained example 以及如何解决它。另一个更微妙的错误是缓存数据而不是承诺创建的竞争条件(这可以避免,但需要两个标志)
    【解决方案2】:

    您可以使用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();
    

    【讨论】:

    • 但原始代码有效,即立即解析。我认为任何强制“解决”仍然异步工作,因为它仍在返回承诺(它只是立即发生)。
    • 所以你可以推迟它不会立即发生,但我认为它不应该像你预期的那样工作
    • 很奇怪。它可以生产,然后调用 then() 并且我们的控制器从“缓存”中获取适当的数据。但是,它仅在测试中中断, then() 永远不会在 Jasmine 环境中被调用。我按照您的建议尝试了 $timeout.flush() 方法,它为测试环境修复了它。我想知道为什么普通版本在测试中不起作用。
    • 可能不是从缓存中获取,而是从服务器获取。您可能会误解它,因为它是从缓存中提取的。你可以调试你的代码(使用断点或console.log)来查看你的模型函数代码的哪一部分被执行了……
    【解决方案3】:

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

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

    【讨论】:

    • 这就是我对promise行为的想法,你可以随时解决。我们的设置是一样的,我们只是称之为数据,而不是缓存。我无法让 then() 真正开火。我按照另一个答案中的建议尝试了 $timeout ,然后做了一个 $timeout.flush() ,它就像一个魅力。请解释如何让 then() 触发。
    • 我不能直接调用它,因为我要确保控制器正确使用它。 SomeCtroller.getData(){someRepository.someGetter.then(function(someData){ //console.log('我被调用')});从不跟踪(在测试中,在生产中工作)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-13
    • 1970-01-01
    • 1970-01-01
    • 2011-10-07
    • 2018-07-03
    • 1970-01-01
    相关资源
    最近更新 更多