Как проверить значение, возвращаемое в Promise от контроллера AngularJS, с помощью Jasmine?

У меня есть контроллер, который предоставляет функцию, которая возвращает некоторый текст после вызова паузы. Он отлично работает, но у меня возникли проблемы с его тестированием с Жасмин. Код внутри обработчика обещаний в тесте никогда не выполняется.

Контроллер:

/* global Q */
'use strict';
angular.module('myModule', ['some.service'])
    .controller('MyCtrl', ['$scope', 'SomeSvc', function ($scope, SomeSvc) {

        $scope.getTheData = function (id) {
            var deferred = Q.defer();
            var processedResult = '';
            SomeSvc.getData(id)
                .then(function (result) {
                    //process data
                    processedResult = 'some stuff';
                    deferred.resolve(processedResult);
                })
                .fail(function (err) {
                    deferred.reject(err);
                });
            return deferred.promise;
        }
    }]);

Тест:

describe('some tests', function() {
    var $scope;
    var $controller;
    var $httpBackend;
    beforeEach(function() {
        module('myModule');
        inject(function(_$rootScope_, _$controller_, _$httpBackend_) {
            $scope = _$rootScope_.$new();
            $controller = _$controller_;
            $httpBackend = _$httpBackend_;
            //mock the call that the SomeSvc call from the controller will make
            $httpBackend.expect('GET', 'the/url/to/my/data');
            $httpBackend.whenGET('the/url/to/my/data')
                .respond({data:'lots of data'});
            $controller ('MyCtrl', {
                $scope: $scope
            });
        });
    });

    describe('test the returned value from the promise', function() {
        var prom = $scope.getTheData(someId);
        prom.then(function(result) {
            expect(result).toBe('something expected');  //this code never runs
        })
    });
});

person binarygiant    schedule 11.10.2014    source источник
comment
Каковы симптомы? Есть ошибки?   -  person alecxe    schedule 12.10.2014
comment
@alecxe симптомы заключаются в том, что код внутри then никогда не запускается, поэтому я не могу утверждать. Обновлен код и вопрос, чтобы лучше отразить проблему. Спасибо.   -  person binarygiant    schedule 12.10.2014
comment
По какой причине вы используете Q, а не $q? И какое обещание возвращает SomeSvc.getData: созданное с использованием $q (например, возвращенное из $http) или одно из Q)?   -  person Michal Charemza    schedule 12.10.2014
comment
В этом проекте используется большой Q, а не предоставленный $q с Angular, потому что в $q отсутствовало то, что предоставил Q. Дизайнерское решение было принято некоторое время назад, подробностей я сейчас не помню. Оба рассматриваемых промиса возвращают промис, созданный большим Q.   -  person binarygiant    schedule 12.10.2014
comment
Похоже, вы используете антишаблон Deferred (хотя вряд ли причина твоих проблем)   -  person Bergi    schedule 12.10.2014
comment
Помог ли вам какой-либо из ответов?   -  person oldwizard    schedule 04.11.2014
comment
На это мог быть такой простой ответ: jasmine.github.io/2.0/< /а>   -  person oligofren    schedule 11.06.2017


Ответы (3)


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

Есть много способов убедиться, что вы не получите такого ложного срабатывания. Примеры:

А) Вернуть обещание

Жасмин будет ждать, пока обещание будет выполнено в течение тайм-аута.

  • Если она не будет решена вовремя, тест провалится.
  • Если обещание отклонено, тест также не будет выполнен.

Осторожно Если вы забудете о возврате, ваш тест даст ложноположительный результат!

describe('test the returned value from the promise', function() {
    return $scope.getTheData(someId)
    .then(function(result) {
        expect(result).toBe('something expected');
    });
});

B) Используйте обратный вызов done, предоставленный Jasmine для тестового метода.

  • Если done не будет вызвано в течение тайм-аута, тест завершится ошибкой.
  • Если done вызывается с аргументами, тест завершится ошибкой.
    Улов здесь передаст ошибку jasmine, и вы увидите ошибку в выводе.

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

describe('test the returned value from the promise', function(done) {
    $scope.getTheData(someId)
    .then(function(result) {
        expect(result).toBe('something expected');
        done();
    })
    .catch(done);
});

C) Использование шпионов и ручного запуска (синхронное тестирование)

Если вы не совершенны, это может быть самый безопасный способ написания тестов.

it('test the returned value from the promise', function() {
    var
      data = { data: 'lots of data' },
      successSpy = jasmine.createSpy('success'),
      failureSpy = jasmine.createSpy('failure');

    $scope.getTheData(someId).then(successSpy, failureSpy);

    $httpBackend.expect('GET', 'the/url/to/my/data').respond(200, data);
    $httpBackend.flush();

    expect(successSpy).toHaveBeenCalledWith(data);
    expect(failureSpy).not.toHaveBeenCalled();
});

Приемы синхронного тестирования
Вы можете вручную запускать httpBackend, тайм-ауты и изменения области действия, когда это необходимо, чтобы заставить контроллер/службы сделать шаг вперед. $httpBackend.flush(), $timeout.flush(), scope.$apply().

person oldwizard    schedule 12.10.2014
comment
. So don't use expect inside then in tests. Это ужасный совет. Научитесь использовать общую поддержку ваших фреймворков для тестирования асинхронного кода. Жасмин: jasmine.github.io/2.0/ Мокко: mochajs.org/#asynchronous-code - person oligofren; 11.06.2017
comment
Ваш комментарий не информативен. Если у вас есть лучший ответ, ответьте на вопрос своим собственным ответом. Документы Jasmine специально не используют .then ни в одном из своих тестов. Ответ был предоставлен в 2014 году, возможно, что-то изменилось. Я по-прежнему считаю, что это действительно плохая идея — вкладывать ожидания в .then. Докажи, что я неправ. - person oldwizard; 13.06.2017
comment
Не информативно? Я напрямую связался с разделами документации по асинхронному тестированию двух самых популярных тестовых фреймворков, чтобы показать, как это делается. Код, который вы тестируете, является асинхронным, но вы просто предоставляете синхронные императивы. Дело в том, что промисы относятся к асинхронному измерению, поэтому, хотя в документации не используются промисы (или генераторы, асинхронность/ожидание и т. д.), в ней показано, как выполнять асинхронное тестовое программирование. Это включает передачу параметра done в асинхронную часть теста. В промисах это обратный вызов Thenable. Например: doSomething().then(done).catch(done). - person oligofren; 13.06.2017
comment
Я с нетерпением жду, когда все ответы StackOverflow будут заменены ссылками на документацию. ;) Легко написать тест с более чистым кодом, все учатся с годами, но не читая документы Jasmine, на которые вы ссылаетесь, они бесполезны в этом случае imo. - person oldwizard; 13.06.2017
comment
Что ж, в любом случае, ваш ответ теперь намного лучше :-) Проголосовал за. - person oligofren; 13.06.2017

В случае, если где-то есть обещание, созданное $q (и поскольку вы, похоже, используете $httpBackend, то это вполне может иметь место), то я предлагаю запустить цикл дайджеста после вызова getTheData и убедиться, что вызов expect не находится в обратном вызове then (чтобы, если что-то сломалось, тест не прошел, а не просто не запустился).

var prom = $scope.getTheData(someId);
$scope.$apply();
expect(result).toBe('something expected');

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

person Michal Charemza    schedule 12.10.2014

Проблема, с которой вы сталкиваетесь, заключается в том, что вы тестируете асинхронный код (на основе обещаний) с использованием синхронного кода. Это не сработает, так как тест завершился до того, как Thenable в вашем тестовом коде начал выполнять обратный вызов, содержащий тест. В документы Jasmine специально показывают, как тестировать асинхронный код (тот же как Mocha), и исправление очень небольшое:

describe('test the returned value from the promise', function(done) {
    var prom = $scope.getTheData(someId);
    prom.then(function(result) {
        expect(result).toBe('something expected');  //this code never runs
        done(); // the signifies the test was successful
    }).catch(done); // catches any errors
});
person oligofren    schedule 13.06.2017
comment
Я не верю, что жасмин сделала это доступным во время публикации вопроса. И/или проект не смог улучшить версию жасмина, которая позволяла это сделать. - person binarygiant; 14.06.2017
comment
@binarygiant Поскольку асинхронность является такой важной частью каждого проекта javascript, Жасмин, конечно же, с самого начала поддерживала асинхронное тестирование :-) Жасмин 1.0 использовала runs, waits и waitsFor, в то время как Жасмин 2.0 (представленный в 2013 г.) выравнивался с Mocha всего за используя обратный вызов, чтобы сделать то же самое. См. jasmine.github.io/2.0/upgrading.html. - person oligofren; 15.06.2017
comment
Да, это здорово. Ответ на вопрос был дан несколько лет назад. Напрягаем ли мы здесь свои снисходительные мускулы? - person binarygiant; 16.06.2017
comment
Господи, чувак, не надо собирать трусики в кучу! Я не пытался быть снисходительным, и если это вышло так, я извиняюсь. Это лучший сайт вопросов и ответов просто потому, что ответы постоянно уточняются. Принятый был неверным, поэтому я добавил правильный. Принятый теперь был улучшен. Такая практика рекомендуется, так как люди по-прежнему находят старые вопросы при поиске в Google. - person oligofren; 16.06.2017
comment
Спасибо за ваш ответ. Вы звучите снисходительно и высокомерно. Работайте над этим. - person binarygiant; 16.06.2017
comment
Спасибо за ничего. - person oligofren; 16.06.2017