Testing Promise Rejections with Chai-As-Promised
The biggest problem I faced was that I needed to mock a third-party API. I avoid stubbing as much as humanly possible, but it is often necessary when testing third party code. I’m a strong believer in that your unit tests should run in an isolated (read: offline) environment. I use Sinon when I need to mock out dependency functionality.
I knew about the lovely chai-as-promised, but I stubbornly resisted adding another dependency to my package.json. After creating a Rube Goldberg machine to test my promises, I gave in and added chai-as-promised to my module. And for a brief moment I was happy.
So I write a test and watch it fail (like the good TDD developer I am). Here’s a simplified psuedo example of what I was working on below.
I watch it fail.
I write the code.
I watch it pass.
Something didn’t feel right. So I decided I wanted to see this test fail again. I added a
not to the
expect statement on line 21 so that it read
return expect(actual).not.to.be.rejectedWith(expected). Unfortunately this also passed.
I realized I wasn’t doing a deep comparison on the rejection. It was simply testing to see if it was indeed an object and passing if it was.
So how do I test if a promise is rejected and also deeply test the rejection?
My coworker Pete Hodgson mentioned that the latest version of
chai-as-promised supports some very declarative syntax and gave me this example:
expect(foo).to.be.rejected.and.eventually.have.property('quxx'). I was overjoyed.
Now I had a way to both test if someting was rejected but also what the rejection actually was.
I updated the above code to now read:
This is not only much easier to read but allows for a deep equals comparison on the rejection.
In summary, there is now a nice and declarative way to test promises and what they actually return using
chai-as-promised. I avoid adding unnecessary dependencies, but I found the benefits here far outweighed the cost of having another dependency.
Thanks for reading and I hope this was helpful!