Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unit testing angularjs $q.all - promise never completes

I'm trying to test a service that I build which uses Angular's $q implementation of Promises. I'm using a combination of Karma, Mocha, Chai, Sinon, Sinon Chai and Chai as Promised.

All the tests that I wrote and return promises are passing but the ones that reject or uses $q.all([ ... ]). I have tried all I could think of but I cannot seem to find where the issue is.

The following is a slim version of what I am testing:

"use strict";


describe("Promise", function () {

    var $rootScope,
        $scope,
        $q;

    beforeEach(angular.mock.inject(function (_$rootScope_, _$q_) {
        $rootScope = _$rootScope_;
        $q = _$q_;
        $scope = $rootScope.$new();
    }));

    afterEach(function () {
        $scope.$apply();
    });

    it("should resolve promise and eventually return", function () {

        var defer = $q.defer();

        defer.resolve("incredible, this doesn't work at all");

        return defer.promise.should.eventually.deep.equal("incredible, this doesn't work at all");
    });

    it("should resolve promises as expected", function () {

        var fst = $q.defer(),
            snd = $q.defer();

        fst
            .promise
            .then(function (value) {
                value.should.eql("phew, this works");
            });

        snd
            .promise
            .then(function (value) {
                value.should.eql("wow, this works as well");
            });

        fst.resolve("phew, this works");
        snd.resolve("wow, this works as well");

        var all = $q.all([
            fst.promise,
            snd.promise
        ]);

        return all.should.be.fullfiled;
    });

    it("should reject promise and eventually return", function () {
        return $q.reject("no way, this doesn't work either?").should.eventually.deep.equal("no way, this doesn't work either?");
    });

    it("should reject promises as expected", function () {

        var promise = $q.reject("sadly I failed for some stupid reason");

        promise
            ["catch"](function (reason) {
                reason.should.eql("sadly I failed for some stupid reason");
            });

        var all = $q.all([
            promise
        ]);

        return all.should.be.rejected;
    });

});

The 3rd, last and the first test are the ones that fail. Actually it does not fail, it just resolves after the timeout is exceeded and I get a Error: timeout of 2000ms exceeded.

EDIT: I have just tried to test with Kris Kowal's implementation of the promises and it works just fine with that.

P.S. I actually found that there is some time spent somewhere in the bowls of either Mocha, Chai or Chai As Promised and the afterEach hook gets called later than the timeout.

like image 961
Roland Avatar asked Nov 05 '14 07:11

Roland


2 Answers

afterEach() is used for cleanup, not for executing code after your preparations but before your tests. $scope.$apply() is not cleanup either.

You need to be doing the following:

// setup async behavior
var all = $q.all(x.promise, y.promise)

// resolve your deferreds/promises
x.reject(); y.reject();

// call $scope.$apply() to 'digest' all the promises
$scope.$apply();

// test the results
return all.should.be.rejected;

You're doing an $apply() AFTER your tests are done, not in between setup and evaluation.

like image 87
ProLoser Avatar answered Oct 20 '22 00:10

ProLoser


I've tried to find out why the tests are not passing even though at first glance they should. Of course I would have to move the $scope.$apply(); from afterEach since that is not the place to call as @proloser mentioned.

Even though I have done that, the tests are still not passing. I've also opened issues on chai-as-promised and angular to see if I get any input/feedback and I've actually been told that it's most likely not to work. The reason is probably because of Angular $q's dependency on the digest phase which is not accounted for in the chai-as-promsied library.

Therefore I've checked the tests with Q instead of $q and it worked just fine, thus strengthening my hypothesis that the fault was not in the chai-as-promised library.

I've eventually dropped chai-as-promised and I've rewritten my test using Mocha's done callback instead (even though behind the scenes, chai-as-promised does the same):

"use strict";


describe("Promise", function () {

    var $rootScope,
        $scope,
        $q;

    beforeEach(angular.mock.inject(function (_$rootScope_, _$q_) {
        $rootScope = _$rootScope_;
        $q = _$q_;
        $scope = $rootScope.$new();
    }));

    it("should resolve promise and eventually return", function (done) {

        var defer = $q.defer();

        defer
            .promise
            .then(function (value) {
                value.should.eql("incredible, this doesn't work at all");
                done();
            });

        defer.resolve("incredible, this doesn't work at all");

        $scope.$apply();

    });

    it("should resolve promises as expected", function (done) {

        var fst = $q.defer(),
            snd = $q.defer();

        fst
            .promise
            .then(function (value) {
                value.should.eql("phew, this works");
            });

        snd
            .promise
            .then(function (value) {
                value.should.eql("wow, this works as well");
            });

        fst.resolve("phew, this works");
        snd.resolve("wow, this works as well");

        var all = $q.all([
            fst.promise,
            snd.promise
        ]);

        all
            .then(function () {
                done();
            });

        $scope.$apply();

    });

    it("should reject promise and eventually return", function (done) {

        $q
            .reject("no way, this doesn't work either?")
            .catch(function (value) {
                value.should.eql("no way, this doesn't work either?");
                done();
            });

        $scope.$apply();

    });

    it("should reject promises as expected", function (done) {

        var promise = $q.reject("sadly I failed for some stupid reason");

        promise
            ["catch"](function (reason) {
                reason.should.eql("sadly I failed for some stupid reason");
            });

        var all = $q.all([
            promise
        ]);

        all
            .catch(function () {
                done();
            });

        $scope.$apply();

    });

});

The above tests will all pass as expected. There might be other ways to do it, but I could not figure out how else, so if anyone else does, it would be great to have it posted so others can benefit from it.

like image 28
Roland Avatar answered Oct 19 '22 23:10

Roland