Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

testing $q with mocha + chai + chai as promised does not work #9954

Closed
rolandjitsu opened this issue Nov 7, 2014 · 15 comments
Closed

testing $q with mocha + chai + chai as promised does not work #9954

rolandjitsu opened this issue Nov 7, 2014 · 15 comments

Comments

@rolandjitsu
Copy link

I have been trying for days to get $q to work with Mocha + Chai + Chai As Promised in Unit Tests, but for some reason, the afterEach hook gets called after the Mocha timeout expires, thus my test fails.

The following is a simple example I have been trying out:

"use strict";


describe.only("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 first, third and last tests are failing because of Error: timeout of 2000ms exceeded. I really cannot figure out what is the issue, but I've also opened up two more issues on both Mocha and Chai As Promised.

It might be just me not calling the digest in the right place or might be the $q implementation or the other I mentioned that are not accounting for something inside the $q implementation.

@pkozlowski-opensource
Copy link
Member

Would be so much easier to help if you would put this into a live plunker...

Anyway, I think that the problem is that you are not calling $scope.$apply(); too late -> try to move it after snd.resolve("wow, this works as well"); and see how it goes.

At any rate this is a support question so would be better asked on StackOverflow. I would be happy to help - provided that you can put together a live reproduce scenario using plunker.

@rolandjitsu
Copy link
Author

@pkozlowski-opensource I have actually asked on stackoverflow and I haven't got an answer yet. And I have tried anything that you mentioned above, but that does not make it work. And I've tried to set up the test on jsfiddle but I cannot seem to make it work. I haven't tried setting up tests in the browser before so I have no clue what is going wrong there, but I do see some errors in the console.

@pkozlowski-opensource
Copy link
Member

@rolandjitsu you didn't link properly back to SO question. As for the reproduce scenario I think jsfiddle is a pain since you've got limited control over initialisation part - this is why I've suggested http://plnkr.co/

@caitp
Copy link
Contributor

caitp commented Nov 7, 2014

I'm reading chai-as-promised, and I'm not seeing how you could possibly expect it to work with $q --- I've asked Domenic if anyones even tried this, but my guess is that it just won't work. Although it looks like it does support jQuery promises, interestingly.

The thing is, it would need to basically monkey patch all of the methods in the promise API, in order to return a custom object, and that is kinda hard to do with $q. I might suggest filing a bug on his repo to get that working.

@rolandjitsu
Copy link
Author

@pkozlowski-opensource my bad, I've made the correction :) I've also made a plnkr fiddle, but I still get the same errors and I am not sure why ...

@rolandjitsu
Copy link
Author

@caitp It should be working since $q is implemented following the Promises/A+ specs. And part of it does work when testing, but I figured it must be a bug in the Chai plugin, just that I cannot see what it is and I've tried debugging it.

@rolandjitsu
Copy link
Author

As it turns out, @caitp you're right. Because the $q service is tied to the digest phase the tests won't work if I use chai-as-promised. I will have to stick with using done to notify mocha that I am having a async test. The following tests work:

"use strict";


describe.only("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.resolve("incredible, this doesn't work at all");

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

        $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");

        $q
            .all([
                fst.promise,
                snd.promise
            ])
            .then(function () {
                done();
            });

        $scope.$apply();
    });

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

        var promise = $q.reject("no way, this doesn't work either?");

        promise
            ["catch"](function (reason) {
                reason.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");
            });

        $q
            .all([
                promise
            ])
            ["catch"](function () {
                done();
            });

        $scope.$apply();
    });

});

I'm not sure, but maybe it would be a good idea to not have $q tied to the digest phase (that would ease writing tests a bit), or maybe that is just the way it should work. Probably this issue can be close, as it's not as much of an issue but more of a feature request I guess.

@telekosmos
Copy link

Hi
I am struggling more or less the same problem trying testing a service.
Regarding the example above, if you replace the first test by wrapping around defer.resolve with a setTimeout just like:

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

        var defer = $q.defer();
        var promise = defer.promise;

        setTimeout(function(){
            defer.resolve("incredible, this doesn't work at all");
        }, 1000);

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

        $scope.$apply();
    }); 

it isn't working for me at all but is confusing whether or not this is either an angular fault or mocha fault.
Please, can anyone confirm this is NOT working like me?

@lgalfaso
Copy link
Contributor

lgalfaso commented Jan 2, 2015

@rolandjitsu this is working as expected.
@telekosmos read https://docs.angularjs.org/api/ngMock/service/$timeout on how to use timeout in tests

@lgalfaso lgalfaso closed this as completed Jan 2, 2015
@sonicoder86
Copy link

Can we mention it in the docs? Consumed hours from my life

@lukeapage
Copy link

I've modified the plinkr to work -

http://plnkr.co/edit/WAvvu99uLhVRmdlwRWDv?p=preview

So that is one way you can use them together.

var $q,
    intervalRef;

beforeEach(module(function(_$exceptionHandlerProvider_) {
  _$exceptionHandlerProvider_.mode('log');
 }));

beforeEach(angular.mock.inject(function (_$rootScope_, _$q_) {
  $q = _$q_;
  intervalRef = setInterval(function(){ _$rootScope_.$apply(); console.log("doing interval");}, 1);
}));

afterEach(function () {
  clearInterval(intervalRef);
});

See #3174 for why you need to set the exceptionHandlerProvider.

@rolandjitsu
Copy link
Author

@lukeapage thanks :)

@aiham
Copy link

aiham commented Jul 29, 2015

@lukeapage Thanks, that did the trick.
@BlackSonic is right, documentation would have been nice.

@mhawila
Copy link

mhawila commented Jun 5, 2016

Not that this adds any value but I find it funny that part of developer struggles this days is to make tests work!! May be it is time we re-examine how we are doing things.

@ShababShahriar
Copy link

Hi @pkozlowski-opensource! I ran into the same problem and could make it work after I created a new scope for the test and did scope.$apply() like you suggested. Can you please explain or point me to resources that explain why it was needed? Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

10 participants