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.
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.
@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.
@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/
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.
@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 ...
@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.
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.
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?
@rolandjitsu this is working as expected.
@telekosmos read https://docs.angularjs.org/api/ngMock/service/$timeout on how to use timeout in tests
Can we mention it in the docs? Consumed hours from my life
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.
@lukeapage thanks :)
@lukeapage Thanks, that did the trick.
@blacksonic is right, documentation would have been nice.
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.
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!
Most helpful comment
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.