Often I write blog posts because I have some issue that I know how to solve. This one struck me as odd. After a stack overflow post about testing promises in Angular, I went down a rabbit hole to figure out how to do it. Here are a few posts:
- Testing Promises with a FakeAsync Zone
- Testing Promises with Real Async Zone
- Testing Promises by listening to a spy
I ran into problems with the order of operations of when catch() and then() functions execute on a promise. I wanted to write about that today.
Review The Code
My other posts went into details of a full setup. You can check out the source code here. I want to review that here.
First, we have some method in a class that calls code with a promise:
2 this.serviceWithPromiseService.methodReturningPromise().then((result) => {
3 this.error = true;
4 }).catch((result) => {
5 this.error = false;
6 });
7}
This methods calls another method that returns a promise. There is an error handler and result handler daisy chained after the promise.
In our spec file, we need to create our own promise, store references to the resolve and reject functions, and then use a spy to replace the promise:
2let rejectFunction: (value: boolean | PromiseLike<boolean>) => void;
3let promise: Promise<boolean>;
4
5beforeEach(() => {
6 promise = new Promise((resolve, reject) => {
7 resolveFunction = resolve;
8 rejectFunction = reject;
9 });
10 spy = spyOn(component.serviceWithPromiseService, 'methodReturningPromise').and.returnValue(promise);
11});
Now we're set up to look at the tests.
Testing the Failure Method
My first attempt was something like this:
2 component.error = true;
3 component.callServiceWithPromise();
4 promise.catch(() => {
5 expect(component.error).toBeFalsy();
6 done();
7 });
8 rejectFunction(false);
9});
Let's dissect this line by line:
- First we set the error value on the component to true. The catch() inside the component should reset it to false, so this makes sure that it will change
- Then we call the service method. This will return the promise we created in the beforeEach because we spy on the internal method.
- Next, we listen to the catch() on the promise. Notice I did not chain the catch() on top of the then() handler; I went straight to the error handler.
- Then we call the reject function, this is where things get weird
- The next thing that happens is the catch function inside the test executes. It sees that the error property is still true and then tests fails
- And finally, the catch() function inside the component class runs, toggling the error propertyon the component.
Why does the order of operations run the catch() function inside the test first? I have no idea.
To get this test to properly execute, i need to include the then() function inside the test:
I banged my head on this for longer than I should have had to. There didn't seem to be an obvious reason as to why chaining the catch() off the then() caused the methods to be executed in different orders.