2

Assuming I have a function which calls two dependend async functions

function targetFn() {
   asyncFn1();
   asyncFn2();
}

This is clearly a defect, since asyncFn2 does not wait for asyncFn1 to be completed. The correct implementation would be:

function targetFn() {
   asyncFn1().then(asyncFn2);
}

However, how to test this? Maybe I test it like this:

it("ensures the ordering", function() {
    spyOn(window, "asyncFn1").and.callFake(function() {
        return $q.reject("irrelevant");
    });
    spyOn(window, "asyncFn2");
    $timeout.flush();
    expect(asyncFn2).not.toHaveBeenCalled();
});

This is okay for this simple test case. But what if I have several chained async functions? The test effort would grow exponential; I have to test every possible combination of rejection and resolving for every promise. What if I change a sync function so that it is an async function afterwards? I would introduce a defect behavior in every callee function without any failing tests.

What I really want is an automatic detection of unhandled promises. Going back to the first example:

function targetFn() {
   asyncFn1();
   asyncFn2();
}

The test system should recognizes that asyncFn1 and asyncFn2 are creating new promises, but that there are no then and catch handler. Also the second example should of course fail:

function targetFn() {
   asyncFn1().then(asyncFn2);
}

Because the then introduces a new promise which is not handled, since it is not returned.

I would like to write a test like:

it("does not introduce unhandled promises", function() {
    expect(targetFn).doesNotIntroduceUnhandledPromises();
});

And it checks if every promise created during the execution of targetFn has either an "endpoint" like a finally or is chained to the promises returned by the targetFn.

Do you have any idea how to achive this? Or even a better solution for this?

Thanks

Timo

Timo
  • 357
  • 1
  • 3
  • 7

1 Answers1

0

This is clearly a defect, since asyncFn2 does not wait for asyncFn1 to be completed. The correct implementation would be:

No it's not, it's pretty common to want to perform two operations in parallel, or even downright ignore one's result:

function loadNewData() {
   reportDataLoadedToGoogleAnalytics();
   return dataService.loadNewData("someParameter");
}

Your issue though, is that your promises aren't being returned. As a rule of thumb whenever a method performs something async with promises, it should return a promise. That would effectively eliminate your problem of testing interesting promises (return them in mocha)

What I really want is an automatic detection of unhandled promises.

That's actually quite possible, you can either use a powerful library like bluebird and swap out $q and use its built in facilities or decorate $q yourself. However when I tried to do these decorations it ended up so hacky I begged satan to take my soul (had to do new Error().stack and grep it for a function name).

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • "No it's not, it's pretty common to want to perform two operations in parallel" . Thats true, but I mentioned that the async operations should be dependent on each other. If I want to execute them in parallel, I would use $q.all. The rule of thumb is senseful, but not an alternative to an unit test, imho. Have you experience in using bluebird or other alternatives for $q? – Timo Apr 29 '15 at 08:57
  • I wrote a Q&A about it a while ago http://stackoverflow.com/questions/23984471/how-do-i-use-bluebird-with-angular – Benjamin Gruenbaum Apr 29 '15 at 08:58
  • Thank you! I will try out if this is an option for me. – Timo Apr 29 '15 at 08:59