如何使用 $httpBackend 对 angularjs 承诺链进行单元测试 [英] How to unit test an angularjs promise chain using $httpBackend

查看:18
本文介绍了如何使用 $httpBackend 对 angularjs 承诺链进行单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Using AngularJS, I am trying to unit test a function that makes multiple calls to $http.

My test looks something like this:

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

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

    $httpBackend.flush();
});

Other single-call tests will register the callback passed to .then() and execute it as soon as I call .flush().

The code under test looks something like this.

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

For what its worth, each of the single calls is unit tested separately. Here, I want to traverse a tree, aggregate some data and unit test a) that the promise chaining is hooked up correctly and b) the aggregation is accurate. Flattening it out into separate discrete calls is already done.

解决方案

I'm a beginner in testing Angular, but I've setup a plnkr that tests a very similar setup to yours with a sucessfull "second" then/promise call

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

The below code snippets are slightly simplified versions of the above plnkr.

The key points I've found are

  • I note the function traverseTheStuff doesn't call $http/$httpBackend at all. It only uses functions defined in $q promises, so the testing on assumes use of $q, and injects that

    var deferred1 = null;
    var deferred2 = null;
    var $q = null;
    
    beforeEach(function() {
      inject(function(_$q_) {
        $q = _$q_;
      });
    });
    
    beforeEach(function() {
      deferred1 = $q.defer();
      deferred2 = $q.defer();
    }
    

  • The functions to be called asynchronously are spied/stubbed with their promise return values, where the promise is created in the test itself, so their actual implementation isn't called when testing traverseTheStuff

    spyOn(MyService,'traverseTheStuff').andCallThrough();
    spyOn(MyService,'getRootData').andReturn(deferred1.promise);
    spyOn(MyService,'getNextLevel').andReturn(deferred2.promise);
    spyOn(MyService,'aggregateTheStuff');
    

  • There aren't any calls to "then" in the test, only to "resolve" on the promises created in the test, followed by $rootScope.$apply(), to then actually call the "then" callbacks in traverseTheStuff, which we can also test are called

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

  • Each promise must be resolved/$apply-ed to call the next "then" function in the chain. So. to get the test to call aggregateTheStuff (or rather, its stub), the second promise, returned from the getNextLevel stub, must also be resolved:

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

An issue with all of the above, is that it assumes certain behaviour from $q and $rootScope. I was under the understanding unit tests like this shouldn't make this assumptions, in order to truly only test one bit of code. I've not worked out how to get around this, or if I'm misunderstanding.

这篇关于如何使用 $httpBackend 对 angularjs 承诺链进行单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆