web-dev-qa-db-fra.com

Tests unitaires Angular Service qui utilise $ timeout avec Jasmine's Mock Clock

J'ai une fonction dans l'un de mes services angular que j'aimerais être appelé à plusieurs reprises à intervalles réguliers. J'aimerais le faire en utilisant $ timeout. Cela ressemble à ceci:

var interval = 1000; // Or something

var _tick = function () {
     $timeout(function () {
        doStuff();
        _tick();
    }, interval);
};

_tick();

Je suis perplexe sur la façon de tester cela avec Jasmine en ce moment - Comment dois-je faire? Si j'utilise $timeout.flush() alors les appels de fonction se produisent indéfiniment. Si j'utilise l'horloge factice de Jasmine, $timeout Ne semble pas être affecté. Fondamentalement, si je peux faire fonctionner cela, je devrais être prêt à partir:

describe("ANGULAR Manually ticking the Jasmine Mock Clock", function() {
    var timerCallback, $timeout;

    beforeEach(inject(function($injector) {
        $timeout = $injector.get('$timeout');
        timerCallback = jasmine.createSpy('timerCallback');
        jasmine.Clock.useMock();
    }));

    it("causes a timeout to be called synchronously", function() {
        $timeout(function() {
            timerCallback();
        }, 100);
        expect(timerCallback).not.toHaveBeenCalled();
        jasmine.Clock.tick(101);
        expect(timerCallback).toHaveBeenCalled();
    });
});

Ces deux variantes fonctionnent, mais ne m'aident pas:

describe("Manually ticking the Jasmine Mock Clock", function() {
    var timerCallback;

    beforeEach(function() {
        timerCallback = jasmine.createSpy('timerCallback');
        jasmine.Clock.useMock();
    });

    it("causes a timeout to be called synchronously", function() {
        setTimeout(function() {
            timerCallback();
        }, 100);
        expect(timerCallback).not.toHaveBeenCalled();
        jasmine.Clock.tick(101);
        expect(timerCallback).toHaveBeenCalled();
    });
});

describe("ANGULAR Manually flushing $timeout", function() {
    var timerCallback, $timeout;

    beforeEach(inject(function($injector) {
        $timeout = $injector.get('$timeout');
        timerCallback = jasmine.createSpy('timerCallback');
    }));

    it("causes a timeout to be called synchronously", function() {
        $timeout(function() {
            timerCallback();
        }, 100);
        expect(timerCallback).not.toHaveBeenCalled();
        $timeout.flush();
        expect(timerCallback).toHaveBeenCalled();
    });
});

Merci d'avance!

43
Words Like Jared

Ne faites pas votre test Async en utilisant l'horloge de Jasmine. À la place, utilisez $timeout.flush() pour maintenir de manière synchrone le flux du test. Cela peut être un peu difficile à configurer, mais une fois que vous l'avez obtenu, vos tests seront plus rapides et plus contrôlés.

Voici un exemple de test qui le fait en utilisant cette approche: https://github.com/angular/angular.js/blob/master/test/ngAnimate/animateSpec.js#L618

52
matsko

La réponse de @ matsko m'a conduit sur la bonne voie. J'ai pensé publier ma solution "complète" pour trouver plus facilement la réponse.

La chose à tester

angular.module("app").service("MyService", function() {
    return {
        methodThatHasTimeoutAndReturnsAPromise: function($q, $timeout) {
            var deferred = $q.defer();
            $timeout(function() {
                deferred.resolve(5);
            }, 2000);
            return deferred.promise;
        }
    };
});

Le test

describe("MyService", function() {
    var target,
        $timeout;
    beforeEach(inject(function(_$timeout_, MyService) {
        $timeout = _$timeout_;
        target = MyService;
    }));
    beforeEach(function(done) {
        done();
    });
    it("equals 5", function(done) {
        target.methodThatHasTimeoutAndReturnsAPromise().then(function(value) {
            expect(value).toBe(5);
            done();
        });
        $timeout.flush();
    });
});
45
Beez