$rootScope.digest не выполняет обещание в Jasmine

В настоящее время мы пытаемся протестировать наши службы angular, которые используют обещания для возврата значений контроллерам. Проблема в том, что функции, которые мы присоединяем к .then, не вызываются в Jasmine.

Мы обнаружили, что добавление $rootScope.digest() в функцию после возврата промиса позволяет вызывать синхронные промисы, однако это по-прежнему не работает для асинхронных промисов.

Код до сих пор

    beforeEach(inject(function (Service, $rootScope)
    {
        service = Service;
        root = $rootScope;
    }));

    it('gets all the available products', function (done)
    {
        service.getData().then(function (data)
        {
            expect(data).not.toBeNull();
            expect(data.length).toBeGreaterThan(0);
            done();
        });
        root.$digest();
    });

В этом случае промис вызывается нормально, но если он асинхронный, он не будет вызван, потому что промис не готов к "перевариванию" к моменту вызова root.$digest().

Есть ли способ узнать, когда обещание после разрешения, чтобы я мог вызвать дайджест? Или, может быть, что-то, что сделает это автоматически? Спасибо ;)

Часть службы, которую мы должны протестировать (обработка ошибок удалена):

var app = angular.module('service', []);

/**
 * Service for accessing data relating to the updates/downloads
 * */
app.factory('Service', function ($q)
{
     ... init

    function getData()
    {
        var deffered = $q.defer();

        var processors = [displayNameProc];

        downloads.getData(function (err, data)
        {
            chain.process(processors, data, function (err, processedData)
            {
                deffered.resolve(processedData);
            });
        });

        return deffered.promise;
    }
    ...

В случае, когда возникает проблема, когда service.getData является асинхронным, обещание разрешается, но функция, прикрепленная к этому обещанию из тестового кода, не вызывается, потому что root.$digest уже был вызван. Надеюсь, это даст больше информации

Временное решение

var interval;
beforeEach(inject(function ($rootScope)
{
    if(!interval)
    {
        interval = function ()
        {
            $rootScope.$digest();
        };
        window.setInterval(interval, /*timing*/);
    }
}));

В итоге я использовал обходной путь, описанный выше, для многократного вызова дайджеста, чтобы каждый слой обещания имел возможность работать... это не идеально и не красиво, но для теста работает...


person BrendanM    schedule 22.09.2015    source источник
comment
Вы издеваетесь над своим сервисом и, следовательно, возвращенным обещанием?   -  person CainBot    schedule 22.09.2015
comment
Нет, я пытался проверить реальный сервис.   -  person BrendanM    schedule 22.09.2015
comment
Если вы тестируете реальную службу, и она внутренне использует службу $http, я полагаю, что angularjs запустит дайджест, когда удаленный вызов будет успешным или неудачным.   -  person CainBot    schedule 22.09.2015
comment
Да, я думаю, что это так, но, к сожалению, мы не используем http-запросы для большинства наших сервисов. На данный момент я могу добавить хак для многократного вызова дайджеста, который вроде как решает эту проблему, но это не очень хорошее решение. Было бы здорово, если бы был какой-то механизм, чтобы сказать, когда обещание было разрешено, но не переварено.   -  person BrendanM    schedule 23.09.2015
comment
Есть ли шанс, что вы можете опубликовать больше кода или создать скрипку/планк?   -  person CainBot    schedule 23.09.2015
comment
Просто добавил немного больше кода, возможно, я мог бы воссоздать проблему на скрипке, но это не было бы с фактическим кодом, который мы используем из-за использования узла webkit.   -  person BrendanM    schedule 23.09.2015


Ответы (1)


Вы должны переместить свои утверждения за пределы, тогда я считаю:

beforeEach(inject(function (Service, $rootScope)
{
    service = Service;
    root = $rootScope;
}));

it('gets all the available products', function (done)
{
    var result;
    service.getData().then(function (data)
    {
        result = data;
        done();
    });

    root.$digest();

    expect(result ).not.toBeNull();
    expect(result .length).toBeGreaterThan(0);
});

Обновить

Разветвил plunkr из комментария ниже, чтобы показать, как можно протестировать асинхронный вызов.

В сервис добавлен $timeout:

var app = angular.module('bad-service', []);

app.service('BadService', function ($q, $interval, $timeout)
{
    this.innerPromise = function(){
      var task = $q.defer();
      console.log('Inside inner promise');

      $timeout(function() {
        console.log('Inner promise timer fired');
        task.resolve();
      }, 500);
      // $interval(function(){

      // }, 500, 1);
      return task.promise;
    }
});

И я очищаю $timeout перед утверждением:

beforeEach(module('test'));


describe('test', function(){
  var service, root, $timeout;

    beforeEach(inject(function (Service, $rootScope, _$timeout_)
    {
      $timeout = _$timeout_;
        console.log('injecting');
        service = Service;
        root = $rootScope;
    }));

    /**
    Test one
    */
    it('Should work', function(done){
      console.log('Before service call');
      service.outerPromise().then(function resolve(){
        console.log('I should be run!');
        expect(true).toBeTruthy();
        done();
      });
      // You will have to use the correct "resolve" here. For example when using:
      // $httpbackend - $httpBackend.flush();
      // $timeout- $timeout.flush();
      $timeout.flush();

      console.log('After service call');
    });
});
person Daan van Hulst    schedule 23.12.2015
comment
К сожалению, это не сработает в нашем случае, потому что getData является асинхронным, что означает, что результат будет нулевым к тому времени, когда он достигнет первого expect(result ).not.toBeNull(); - person BrendanM; 06.01.2016
comment
Это решение предназначено для асинхронных вызовов; вы проверили, если это не работает? Комбинация 'готово();' и 'root.$digest();' позаботится о том, чтобы ваш асинхронный вызов был разрешен до вызова ожиданий. - person Daan van Hulst; 06.01.2016
comment
Да, извините, я вижу, что сейчас я попробую - person BrendanM; 06.01.2016
comment
Только что попробовал, я думаю, что это похоже на то, что у меня было изначально, и, к сожалению, оно по-прежнему не вызывает функцию then, потому что внутри service.getData есть вложенный вызов обещания, который не «переваривается», что означает, что внешний вызов никогда не разрешается Итак, что происходит: 1) вызывается getData 2) вызывается дайджест 3) обещание не разрешено из-за вложенного обещания 4) ожидание вызывается с нулевым значением... afaik ваше решение должно работать, но должно быть что-то еще, что я делаю не понял. - person BrendanM; 06.01.2016
comment
Не могли бы вы создать plunkr? Потом посмотрю в чем может быть дело. - person Daan van Hulst; 06.01.2016
comment
Добавлено одно: plnkr.co/edit/hVO1WjQrtIbfdYd0AAqE?p=preview Не знаю если он точно охватывает наш случай, но показывает некоторые проблемы, которые у нас были. - person BrendanM; 06.01.2016
comment
Plunkr, кажется, не работает для меня. Я получаю сообщение об ошибке: «Uncaught ReferenceError: beforeEach не определен». Это связано с тем, что: "Смешанный контент: страница "run.plnkr.co/NSMWNpPB1Yb3hOrp" была загружен через HTTPS, но запросил небезопасный скрипт 'cdnjs.cloudflare. com/ajax/libs/jasmine/2.2.1/jasmine.js'. Этот запрос был заблокирован; контент должен передаваться через HTTPS.'. Я исправил это, изменив «http» на «https» для jasmine. - person Daan van Hulst; 06.01.2016
comment
Да, я думаю, что это помогает, спасибо: D ... некоторое время застрял на этом. Мне нужно обновить все мои взломанные тесты сейчас, но хорошо, что есть решение. - person BrendanM; 06.01.2016