问题
I'm trying to get chai-as-promised
to work with $q
promises with karma unit tests.
svc.test = function(foo){
if (!foo){
// return Promise.reject(new Error('foo is required'));
return $q.reject(new Error('foo is required'));
} else {
// get data via ajax here
return $q.resolve({});
}
};
it.only('should error on no foo', function(){
var resolvedValue = MyServices.test();
$rootScope.$apply();
return resolvedValue.should.eventually.be.rejectedWith(TypeError, 'foo is required');
});
The unit test just times out. I am not sure what I'm doing wrong here to get the promise to resolve properly. It seems to be an issue with using $q
-- when I use native Promise.reject()
it works fine.
I filed a ticket here, but nobody seems to be responding: https://github.com/domenic/chai-as-promised/issues/150
回答1:
The way chai-as-promised
expects to modify promise assertions is transferPromiseness method.
By default, the promises returned by Chai as Promised's assertions are regular Chai assertion objects, extended with a single then method derived from the input promise. To change this behavior, for instance to output a promise with more useful sugar methods such as are found in most promise libraries, you can override chaiAsPromised.transferPromiseness.
For Angular 1.3+ support, $q
promises can be duck-typed by $$state
property, so native promises won't be affected:
chaiAsPromised.transferPromiseness = function (assertion, promise) {
assertion.then = promise.then.bind(promise);
if (!('$$state' in promise))
return;
inject(function ($rootScope) {
if (!$rootScope.$$phase)
$rootScope.$digest();
});
};
chaiAsPromised
chains each asserted promise with then
. Even if the promise is settled, the rest of the chain still requires the digest to be triggered manually with $rootScope.$digest()
.
As long as the spec contains no asynchronous code, it becomes synchronous, no promise is required to be returned:
it('...', () => {
...
expect(...).to.eventually...;
expect(...).to.eventually...;
});
And is equal to mandatory $rootScope.$digest()
after each set of eventually
assertions/expectation when transferPromiseness
wasn't set:
it('...', () => {
...
expect(...).to.eventually...;
expect(...).to.eventually...;
$rootScope.$digest();
});
回答2:
You need to change the order of execution in your tests. Asynchronous tasks with chai-as-promised need to happen before the expectation.
it('does not work', () => {
$timeout.flush();
expect(myAsyncTask()).to.eventually.become('foo');
})
it('does work', () => {
expect(myAsyncTask()).to.eventually.become('foo');
$timeout.flush();
})
You need to initiate the call to the asynchronous task before flushing the queue of asynchronous tasks.
Also, don't use $rootScope.$digest
. That may have other side effects that are not desirable within your test(s).
$timeout.flush
is what you're looking for.
https://docs.angularjs.org/api/ngMock/service/$timeout
To get your specific test(s) working:
it('should error on no foo', function(){
MyServices.test().should.eventually.be.rejectedWith(TypeError, 'foo is required')
$rootScope.$apply();
});
it('should pass on foo', function(){
MyServices.test('foo').should.eventually.become({});
$rootScope.$apply();
}
tl;dr
it('async test', () => {
setup();
expect();
execute();
})
it('sync test', () => {
setup();
execute();
expect();
})
Given the comments posted:
Should it be mentioned that it is unethical to downvote 'rival' answers on the question you're answering?
Fair enough. I think the answer is misleading, given that there is no extra setup necessary to get chai-as-promised working with Angular without having to deal with the done
callback. Fwiw, I'll go ahead and try to revoke said downvote and be ethical about it.
The OP has no signs of timeout in his code and doesn't state that the task is asynchronous.
$rootScope.$digest()
has no side effects in specs when called outside of scope digest. The reason why it is not recommended in production is because it doesn't have the safeguards that$apply
has.
$rootScope.$digest
is effectively the same as $rootScope.$apply
(and $scope.$apply
for that matter). source
$timeout.flush
will flush non-$timeout
based functions just as well. It is not exclusive to $timeout
based functions.
Plunker to showcase how it just works™: plunker
来源:https://stackoverflow.com/questions/36705042/chai-as-promised-tests-dont-work-with-q-promises