如何使用$ httpBackend对angularjs promise链进行单元测试

时间:2013-12-06 05:07:03

标签: unit-testing angularjs jasmine promise

使用AngularJS,我试图对一个多次调用$ http的函数进行单元测试。

我的测试看起来像这样:

it('traverses over a hierarchical structure over multiple chained calls', function() {

    myService.traverseTheStuff()
    .then(function(theAggregateResult) {
        // ...is never fulfilled
    });

    $httpBackend.flush();
});

其他单次调用测试将注册传递给.then()的回调,并在我调用.flush()后立即执行。

正在测试的代码看起来像这样。

function traverseTheStuff(){

    // This will make a call to $http to fetch some data
    return getRootData()

    // It is fulfilled at the end of the test when I $httpBackend.flush()
    .then(function(rootData){

        // Another call to $http happens AFTER $httpBackend.flush()
        return getNextLevel(rootData.someReference);
    })

    // The second promise is never fulfilled and the test fails
    .then(function(nextLevel){
        return aggregateTheStuff(...);
    });
}

为了它的价值,每个单独的呼叫都是单独测试的。在这里,我想遍历一棵树,聚合一些数据和单元测试a)承诺链正确连接和b)聚合是准确的。将其展平为单独的离散调用已经完成。

1 个答案:

答案 0 :(得分:18)

我是测试Angular的初学者,但是我已经设置了一个plnkr,用一个成功的“秒”然后/保证呼叫来测试与你的设置非常相似的设置

http://plnkr.co/edit/kcgWTsawJ36gFzD3CbcW?p=preview

以下代码片段是上述plnkr的略微简化版本。

我发现的关键点是

  • 我注意到函数traverseTheStuff根本没有调用$ http / $ httpBackend。它只使用$ q promises中定义的函数,因此测试假定使用$ q,并注入

    var deferred1 = null;
    var deferred2 = null;
    var $q = null;
    
    beforeEach(function() {
      inject(function(_$q_) {
        $q = _$q_;
      });
    });
    
    beforeEach(function() {
      deferred1 = $q.defer();
      deferred2 = $q.defer();
    }
    
  • 异步调用的函数使用其promise返回值进行间谍/存根,其中promise在测试本身中创建,因此在测试traverseTheStuff时不会调用它们的实际实现

    spyOn(MyService,'traverseTheStuff').andCallThrough();
    spyOn(MyService,'getRootData').andReturn(deferred1.promise);
    spyOn(MyService,'getNextLevel').andReturn(deferred2.promise);
    spyOn(MyService,'aggregateTheStuff');
    
  • 在测试中没有任何“then”的调用,只是为了“解析”测试中创建的promises,然后是$ rootScope。$ apply(),然后实际调用“then” “traverseTheStuff中的回调,我们也可以测试它们被称为

    beforeEach(function() {
      spyOn(deferred1.promise, 'then').andCallThrough();
    });
    
    beforeEach(function() {
      deferred1.resolve(testData);
      $rootScope.$apply(); // Forces $q.promise then callbacks to be called
    });
    
    it('should call the then function of the promise1', function () { 
      expect(deferred1.promise.then).toHaveBeenCalled();
    });
    
  • 必须解决每个承诺/ $ apply-ed以调用链中的下一个“then”函数。所以。要获得测试以调用aggregateTheStuff(或者更确切地说,它的存根),还必须解析从getNextLevel存根返回的第二个promise:

    beforeEach(function() {
      deferred2.resolve(testLevel);
      $rootScope.$apply(); // Forces $q.promise then callbacks to be called
    });
    
    it('should call aggregateTheStuff with ' + testLevel, function () {
      expect(MyService.aggregateTheStuff).toHaveBeenCalledWith(testLevel);
    });
    

以上所有问题都是它假定来自$ q和$ rootScope的某些行为。我是在理解单元测试这样的假设不应该做出这样的假设,以便真正只测试一点代码。我没有弄清楚如何解决这个问题,或者我是否误解了。