Как да тествам стойност, върната в Promise от AngularJS Controller с Jasmine?

Имам контролер, който излага функция, която връща някакъв текст след извикване за почивка. Работи добре, но ми е трудно да го тествам с 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 предостави. Дизайнерското решение беше взето преди известно време и в момента не си спомням подробности. И двете въпросни обещания връщат обещание, създадено от big Q.   -  person binarygiant    schedule 12.10.2014
comment
Изглежда, че използвате Deferred antipattern (въпреки че едва ли е причината за вашите проблеми)   -  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 няма да бъде изпълнено, освен ако не бъдат извикани обратните извиквания на обещанието - което е риск за фалшив положителен резултат, какъвто сте изпитали тук. Тестът ще премине тук, тъй като очакването никога не е било изпълнявано.

Има много начини да се уверите, че няма да получите фалшив положителен резултат като този. Примери:

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

Jasmine ще изчака обещанието да бъде разрешено в рамките на времето за изчакване.

  • Ако не бъде решен навреме, тестът ще бъде неуспешен.
  • Ако обещанието бъде отхвърлено, тестът също ще се провали.

Внимание Ако забравите връщането, тестът ви ще даде фалшиво положителен резултат!

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
Не е информативен? Свързах се директно към секциите на документ за асинхронно тестване на двете най-популярни тестови рамки, за да покажа как се прави. Кодът, който тествате, е асинхронен, но вие просто предоставяте синхронни императиви. Въпросът тук е, че обещанията принадлежат към асинхронното измерение, така че въпреки че документите не използват обещания (или генератори, async/await и т.н. ...), те показват как да правите асинхронно тестово програмиране. Това включва предаване на параметъра 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
Не вярвам, че jasmine направи това достъпно по времето, когато въпросът беше публикуван. И/или проектът не успя да преодолее версията жасмин, която позволяваше да бъде направено. - person binarygiant; 14.06.2017
comment
@binarygiant Тъй като асинхронността е толкова голяма част от всеки javascript проект, Jasmine, разбира се, имаше поддръжка за async тестване от самото начало :-) Jasmine 1.0 използва runs, waits и waitsFor, докато Jasmine 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