Как выполнить модульное тестирование цепочки обещаний angularjs, используя $httpBackend

Используя 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(...);
    });
}

Как бы то ни было, каждый из отдельных вызовов тестируется отдельно. Здесь я хочу пройтись по дереву, агрегировать некоторые данные и модульно протестировать: а) что цепочка обещаний подключена правильно и б) агрегация точна. Сведение его в отдельные дискретные вызовы уже сделано.


person Craig Celeste    schedule 06.12.2013    source источник


Ответы (1)


Я новичок в тестировании Angular, но я настроил plnkr, который тестирует очень похожую настройку на вашу с успешным "вторым" вызовом then/promise.

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

Приведенные ниже фрагменты кода представляют собой слегка упрощенные версии приведенного выше plnkr.

Ключевые моменты, которые я нашел, это

  • Я отмечаю, что функция traverseTheStuff вообще не вызывает $http/$httpBackend. Он использует только функции, определенные в промисах $q, поэтому тестирование предполагает использование $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();
    }
    
  • Функции, которые будут вызываться асинхронно, отслеживаются/заглушаются их возвращаемыми значениями обещания, где обещание создается в самом тесте, поэтому их фактическая реализация не вызывается при тестировании traverseTheStuff.

    spyOn(MyService,'traverseTheStuff').andCallThrough();
    spyOn(MyService,'getRootData').andReturn(deferred1.promise);
    spyOn(MyService,'getNextLevel').andReturn(deferred2.promise);
    spyOn(MyService,'aggregateTheStuff');
    
  • В тесте нет никаких вызовов «тогда», только «разрешить» обещания, созданные в тесте, за которыми следует $rootScope.$apply(), чтобы затем фактически вызвать обратные вызовы «тогда» в 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 для вызова следующей функции «тогда» в цепочке. Так. чтобы тест вызывал агрегатTheStuff (точнее, его заглушку), второе обещание, возвращаемое из заглушки getNextLevel, также должно быть разрешено:

    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. Я понимал, что модульные тесты, подобные этому, не должны делать таких предположений, чтобы действительно тестировать только один бит кода. Я не придумал, как обойти это, или я неправильно понимаю.

person Michal Charemza    schedule 06.12.2013
comment
Большое спасибо! Это выглядит многообещающе. Я также только начинаю изучать angular, jasmine и тестировать javascript. Я прочитаю о spyOn и переварю ваше решение сегодня вечером. - person Craig Celeste; 06.12.2013