Testing with async/await
Watch Sam convert EmberMap's test suite to use async/await
Transcript
Ever since async/await landed in ECMAScript 2017, it's actually been possible to switch our Ember tests over to use it. Let's do it now for EmberMap's codebase.
I've checked out a new branch from master here called async-await-tests
, and my test suite is passing.
The first thing we need to do is download a polyfill for async functions. Fortunately there's an addon that does this for us, so we'll install it with
ember install ember-maybe-import-regenerator
Now you might already be using another library that depends on this like ember-concurrency, but because we're using async/await in our application now, we want to add it to our top-level package.json
file.
A nice side effect of this is that you can also use async/await in your Ember application code.
Ok, next we want to make sure the async/await polyfill plays nice with Ember. The polyfill uses window.Promise
but we want it to use RSVP, which is the Promise library used by Ember. So we'll shim the global Promise with RSVP's version using an initializer.
Let's create a new initializer by running
ember g initializer set-global-promise-for-async-await
Then we'll open the initializer and point the window's promise to RSVP's. We'll add a note here as well.
import RSVP from 'rsvp';
export function initialize(/* application */) {
// This forces the async/await polyfill to use RSVP.Promise
window.Promise = RSVP.Promise;
}
Great, let's come over and start updating our tests. We'll open our first acceptance test. The nice thing about this is you can do it piece-by-piece, since classic-style tests will still work.
Let's start the test server and filter it down to this module. The module is passing, so let's work on the first test.
To use async/await, first we mark the function as an async function. You'll see when we do this we get a red line - this is ESLint complaining. To fix this, I'll open my .eslint
file here and set the ecmaVersion
to 2017. (Your app may already have this set).
Now, back in our test we can get to work. Everywhere we're using an asynchronous test helper, we just need to add the await
keyword in front of it. Finally, the best part — we get to remove the andThen
function and just write our assertions like normal at the end of our test.
-test('Unauthenticated users see a link to subscribe', function(assert) {
- visit('/');
- click(testId('hamburger-button'));
+test('Unauthenticated users see a link to subscribe', async function(assert) {
+ await visit('/');
+ await click(testId('hamburger-button'));
- andThen(function() {
- assert.elementExists(testId('subscribe-link'));
- });
+ assert.elementExists(testId('subscribe-link'));
});
Let's save this and check our tests. Great, everything's still passing!
Async/await is nice because it makes it very clear which functions in our code are asynchronous, and which are not. The classic test helpers work by using a global promise chain that automatically waits for us, but this is a little hard to understand, especially for new folks. Now, as async/await becomes more ubiquitous, our test functions will feel familiar and look like every other asynchronous function in our system.
Ok, let's convert the rest of these over. And now our whole module is passing on async/await tests.
Now, I want to convert the rest of my acceptance tests over, but this was a pretty manual process and not very exciting. Fortunately, Ember Watson has our back covered.
If we come to the readme and scroll down, we see a command for converting classic tests to async/await. If you've never Watson before you can install with
yarn global add ember-watson
(or npm install -g, if you're using npm). And now you have access to ember-watson
. Now the command we want is called convert-tests-to-async-await
. Let's come to our app, and type in
ember-watson convert-tests-to-async-await
Now that was cool. If we one of these up we'll see that Watson has done all of the tedious work for us!
Let's run the test suite and make sure everything passes.
We've got one failure, and it's in our auto-play module. I can actually spot it right here in this test - we are using a custom pauseVideo
test helper, and if we pop this open we can see it's using registerAsyncHelper
. So if you have custom async helpers you're using, Watson doesn't know about them and so we need to come back here and add the await
keyword ourselves.
Once we do, this test passes and now our entire suite is green. Not bad!
Now, Watson is an awesome tool and it did exactly what we wanted here. But EmberMap's codebase is relatively small and I understand it well. If I were working on a larger more complex app with lots of people, I'd probably just convert my tests over one at a time as I was working on them, since there's no harm in keeping around the old tests around.
Async/await is an awesome new JavaScript feature, and converting your tests over is a great way to get some experience with it and clean your code.