J'ai un service angulaire qui a une dépendance asynchrone comme celle-ci

(function() {
    angular
        .module('app')
        .factory('myService', ['$q', 'asyncService',

    function($q, asyncService) {

        var myData = null;

        return {
            initialize: initialize,
        };

        function initialize(loanId){
            return asyncService.getData(id)
                .then(function(data){
                    console.log("got the data!");
                    myData = data;
            });
        }
    }]);
})();

Je veux tester de manière unitaire la fonction initialize et j'essaie en jasmin comme ceci:

describe("Rate Structure Lookup Service", function() {

    var $q;
    var $rootScope;
    var getDataDeferred;
    var mockAsyncService;
    var service;

    beforeEach(function(){
        module('app');

        module(function ($provide) {
            $provide.value('asyncService', mockAsyncService);
        });

        inject(function(_$q_, _$rootScope_, myService) {
            $q = _$q_;
            $rootScope = _$rootScope_;
            service = myService;
        });

        getDataDeferred = $q.defer();

        mockAsyncService = {
            getData: jasmine.createSpy('getData').and.returnValue(getDataDeferred.promise)
        };
    });

    describe("Lookup Data", function(){
        var data;

        beforeEach(function(){
            testData = [{
                recordId: 2,
                effectiveDate: moment("1/1/2015", "l")
            },{
                recordId: 1,
                effectiveDate: moment("1/1/2014", "l")
            }];
        });

        it("should get data", function(){
            getDataDeferred.resolve(testData);

            service.initialize(1234).then(function(){
                console.log("I've been resolved!");
                expect(mockAsyncService.getData).toHaveBeenCalledWith(1234);
            });

            $rootScope.$apply();
        });
    });
});

Aucun des messages de la console n'apparaît et le test semble simplement passer sans que les promesses ne soient jamais résolues. Je pensais que le $rootScope.$apply() le ferait mais semble ne pas le faire.

MISE À JOUR

@estus avait raison de dire que $rootScope.$appy() est suffisant pour déclencher la résolution de toutes les promesses. Il semble que le problème résidait dans ma moquerie de asyncService. Je l'ai changé de

mockAsyncService = {
    getData: jasmine.createSpy('getData').and.returnValue(getDataDeferred.promise)
};

À

mockAsyncService = {
    getData: jasmine.createSpy('getData').and.callFake(
        function(id){
            return $q.when(testData);
    })
};

Et j'ai défini testData ce dont j'ai besoin pour les tests plutôt que d'appeler getDataDeferred.resolve(testData). Avant ce changement, le mockAsyncService était injecté mais la promesse de getDataDeferred n'était jamais résolue.

Je ne sais pas si c'est quelque chose dans l'ordre d'injection dans le beforeEach ou quoi. Encore plus curieux, c'est que cela doit être un callFake. L'utilisation de .and.returnValue($q.when(testData)) bloque toujours la résolution des promesses.

5
Brian Triplett 5 mars 2016 à 01:18

3 réponses

Meilleure réponse

Les promesses angulaires sont synchrones lors des tests, $rootScope.$apply() suffit pour les faire s'installer à la fin de la spécification.

À moins que asyncService.getData ne retourne une vraie promesse au lieu de $q promesse (et ce n'est pas le cas dans ce cas), l'asynchronicité n'est pas un problème dans Jasmine.

La bibliothèque Correspondants de promesse Jasmine est exceptionnellement bonne pour tester les promesses angulaires. Outre le manque évident de verbosité, il fournit de précieux commentaires dans de tels cas. Alors que ce

rejectedPromise.then((result) => {
  expect(result).toBe(true);
});

Spec passera quand il ne devrait pas, ce

expect(pendingPromise).toBeResolved();
expect(rejectedPromise).toBeResolvedWith(true);

Échouera avec un message significatif.

Le problème réel avec le code de test est la priorité dans beforeEach. Le processus d'amorçage angulaire n'est pas synchrone.

getDataDeferred = $q.defer() doit être placé dans le bloc inject, sinon il sera exécuté avant le démarrage du module et l'injection de $q. Il en va de même pour mockAsyncService qui utilise getDataDeferred.promise.

Dans le meilleur des cas, le code générera une erreur car la méthode defer a été appelée sur undefined. Et dans le pire des cas (ce qui explique pourquoi les propriétés de spécification comme this.$q sont préférables aux variables de suite locales) $q appartient à un injecteur de la spécification précédente, donc $rootScope.$apply() n'aura pas effet ici.

6
Estus Flask 5 mars 2016 à 01:36

Vous devez passer le paramètre facultatif done à la fonction de rappel dans votre bloc informatique. Sinon, jasmine n'a aucun moyen de savoir que vous testez une fonction asynchrone - les fonctions asynchrones reviennent immédiatement.

Voici le refactor:

it("should get data", function(done){

    service.initialize(1234).then(function(){
        console.log("I've been resolved!");
        expect(mockAsyncService.getData).toHaveBeenCalledWith(1234);
        done();
    });  
});
5
Tate Thurston 4 mars 2016 à 22:48

Voici quelques pointeurs (feuilletés, à l'arrière de la bière). Malheureusement, je n'ai aucun moyen de savoir s'il s'agit d'erreurs réelles ou s'il s'agit de "fautes de frappe" car vous avez "simplifié" le code.

Tout d'abord, il n'y a aucune raison de ne pas fournir asyncService en tant que service et en ligne. Essaye ça:

$provide.service('asyncService', function() {
    // asyncService implementation
});

De plus, je ne pense pas que cette injection de dépendance fonctionnerait.

inject(function(_$q_, _$rootScope_, myService) {
    $q = _$q_;
    $rootScope = _$rootScope_;
    service = myService;
});

Parce que le conteneur DI ne connaît pas myServiceProvider. Vous pouvez essayer ceci à la place:

inject(function(_$q_, _$rootScope_, _asyncService_) {
    $q = _$q_;
    $rootScope = _$rootScope_;
    service = _asyncService_;
});

Cela fonctionnerait parce que vous avez appelé $ provide plus tôt avec 'asyncService' comme paramètre.

De plus, vous n'utilisez pas correctement l'api $ promise. Vous ne renvoyez pas une résolution () «promise» au .then () dans votre test unitaire. Essayez d'utiliser une implémentation alternative pour asyncService similaire à ceci:

$provide.service('asyncService', function() {
    this.getData = function() {
        return $q(function(resolve, reject) {
         resolve('Promise resolved');
        });
    }
});

Consultez les documents pour $ q

Vous pourriez espionner cela dans votre test unitaire comme celui-ci. Il n'y a aucune raison d'appeler l'espion dans votre fonction beforeEach ().

jasmine.spyOn(service, 'getData').and.callThrough();

Votre attente () semble bonne.

Faites-moi savoir si tout cela vous aide.

0
glcheetham 4 mars 2016 à 22:40