Как unit test цепочка обещаний angularjs с использованием $httpBackend

Используя AngularJS, я пытаюсь выполнить unit test функцию, которая делает несколько вызовов $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(...);
    });
}

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

Ответ 1

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

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