In unit tests, how are `setTimeout` and `$scope.apply()` completing the promises?

Imported Question by estelle · Aug 25, 2016 at 07:48 AM

See for instance : https://github.com/medic/medic-webapp/blob/master/…

A triple layer of them!

I understand that they propagate the promises, but don’t understand why.

1 Like

Two different things here.

Why use $scope.apply → because of $q
$q is an angular thing : it’s coupled with the digest cycle. Each time the digest cycle runs it resolves the promises.
In the test env, there’s no digest cycle, so the promises just hang.
Calling $scope.digest() triggers the digest cycle to unblock them.

Why use setTimeout → to resolve promises
When using real promises in tests, we want the execution to leave the current test, go execute what’s in the queue (i.e. the stuff in the promises) and then come back to the test.
setTimeout will do that jumping, and without a wait time value it will come back as soon as possible (no use added unnecessary wait time in tests).
If you have a chain of promises, you will need to do that one time per promise in the chain.
Note that this is flaky! The execution is probably jumping to something else, but you don’t know for sure if it’s executing your promises or something else.

Alternatives to this uglyness

  • use Q instead of $q in tests
    $q is a wrapper around Q. It adds a bunch of things, including binding promises to the digest cycle.
    Using Q instead of $q removes the binding and thus the need for $scope.apply. (example)
    Beware though that some of the stuff you’re counting on in $q might not be there in Q… Surprise.

  • use fake promises
    Use a synchronous object with the same api as a promise, so that your code is actually synchronous.

2 Likes