If you have ever wondered how to test promises in Angular this post is for you.
Assume we have a function which in turn invokes two other functions. Here submitReport
will invoke two services. Our report
will be persisted via reportsGateway
and, if successful, the router
will navigate the user to a list of reports.
A developer not used to testing asynchronous code might write a unit test as follows
Running the test leads to
Why?
We can add a log statement into the anonymous then
function and another one above our assertions. When running the tests we witness
We have a timing issue. The assertion happens before the promise is resolved.
The quick and dirty solution is to add a timeout to the test after calling submitReport
. It gives the promise enough time to resolve before checking the assertions.
Run the test and its passing. Sweet. Let’s continue with the next feature…
Wait
Change the assertion on the router
to state the opposite. Look at the added not
Run it. The tests are still passing. How?
Because our test finishes without ever running the anonymous function in setTimeout
. Thus the test has 0 assertions.
We can fix it by telling the test case to wait. Jasmine offers Asynchronous Support via done
. A test will not complete until done
is called.
Our test is back to green and fails as soon as we try to swap toHaveBeenCalledWith(['reports'])
with not.toHaveBeenCalledWith(['reports'])
.
Sadly, by going for the timeout solution, we have made our test unnecessarily slow. The test will wait these 10ms even if less would be fine too.
We can give the test exactly the amount it needs by using the Promise already returned by submitReport
One issue I have with the above is the fact that submitReport
has to return a Promise
for it to work. What if we do not care about the return value? Additionally it intermingles the Act and Assert part in Arrange Act Assert and adds another level of indentation which decreases the readability.
Thankfully Angular offers fakeAsync to test code as if it were synchronous. The promise is fulfilled immediately after you call tick()
The test is readable, not flaky, not wasting time and passes. Great.