Asynchronous unit testing with MochaJS and ECMAScript 2016 (ES7) async/await
This article assumes that the reader knows ECMAScript 2015 (ES6) syntax and MochaJS and is currently trying to refactor their asynchronous tests to ECMAScript 2016 (ES7).
Let’s assume we already have a TDD’d method called getFruits and it calls fetch to that will then respond with a collection (array) of fruits.
The corresponding unit test for this looks like this.
Before we begin refactoring our unit test, let’s have a quick breakdown explaining why the fruits repository is being tested this way. You might also notice that I am using the BDD Given-When-Then concept of writing test descriptions. This helps organize our test and makes it a lot easier to grok.
We import four modules in our unit test file.
We want to stub fetch with sinon because it is an external asynchronous call. We do this by stubbing the fetch method in the global object.
We will not stub fruits-repository because it is the System Under Test (SUT).
Never stub the system that you are testing. Ever.
You never stub the system that you are testing because by stubbing it you are then modifying the behaviour of the system that you want to test. By modifying the behaviour of your SUT, you are no longer able to test its actual behaviour which is the primary reason why you are writing a unit test anyway.
Before each test starts we will sandbox sinon stubs so that each stub that’s going to be used inside a test will be isolated. We then restore it to its original functionality after each test.
Our first test is all about verifying that we actually call the fetch service every time we call getFruits.
Given the fruits repository. When getting a collection of fruits. It should call fetch.
Now that we’ve verified that we actually call fetch every time we try to get a collection of fruits, it’s time to find out if we get the data when the call is successful.
Since we’ve stubbed fetch we need to stub some of the methods that we need inside of it like json. By definition it returns a promise that resolves with an object literal containing the JSON data.
We also need to make sure that our resolving our promise will never run our catch clause. So we add a failing test where we check that false is equal to true.
Given the fruits repository. When getting a collection of fruits and is successful. It should return an array of fruits.
Now that we know that we’re getting the right data. We need to find out if we’re going to get the right error message when getting a collection of fruits is unsuccessful.
Just like our successful test, we need to make sure that rejecting the promise does not run the then clause.
Given the fruits repository. When getting a collection of fruits and is unsuccessful. It should return an error object.
tl;dr
- Test if fetch is called with exactly the right arguments
- Test the happy path — when service is successful
- Test the sad path — when service is unsuccessful
It’s now time to refactor our source file and its corresponding unit test.
We already have Promise why do we need async/await?
async/await lets you feel like you’re writing synchronous code while still writing asynchronous code. For simple queries a Promise is easy to understand. However, as soon as requirements start getting more complex, having a synchronous feel greatly reduces complexity.
Now that we’re using async/await we can go back to using try/catch/finally which makes it feel like synchronous code.
Just like the source file we will refactor our unit test.
Since can now use finally you will notice that our first test is now a lot easier to understand. The finally clause executes after the try and catch clauses executes. This is exactly what you need because regardless if your promise resolves or not, you want to know if fetch is called once and with exactly the right arguments.
The same goes for our successful test expect instead of finally we’ll make sure that our catch clause never runs. And if it does, we should get alerted by a failing test.
As far as our unsuccessful test is concerned the try clause will always execute so there is no need to have an expected failing test.
And there you have it. You’ve refactored your asynchronous code to using async/await.