Testing Ember Applications in 2018
At 201 Created, we’ve been busy working on several projects. Some involve Ember.js apps that have been around for a while, and we’ve also been lucky to run ember new
a few times recently to create some brand new Ember apps. This has given us the opportunity to look at some exciting changes and new tools that are emerging in the Ember community around testing, and either migrate our old apps, or start our new apps with all the latest best practices.
I’m going to cover three recent changes in this post:
- Ember Native DOM Helpers
- Running tests with Headless Chrome and/or Firefox instead of PhantomJS
- Ember Test Selectors
Ember Native DOM Helpers
We love the testing story in Ember, but it can annoying that you have to use different syntax for writing acceptance tests versus component integration tests. Fortunately, the community agrees and has a Grand Testing Unification RFC. There’s already a fantastic and well-documented addon called ember-native-dom-helpers by Miguel Camba that implements identical helpers for both acceptance and integration tests, using the syntax suggested by the RFC. Eventually, those helpers will be available directly from the Ember Test Helpers addon which is included with all Ember apps. We decided to try migrating one of our older apps.
The addon takes many helpers that we are accustomed to using in acceptance tests, such as click(), fillIn(), keyEvent(), blur()
, and makes them available with the same semantics in integration tests.
The best thing about the addon is that all the helpers are async-aware and spawn a runloop when necessary. If you’ve ever had an integration test that looked like this:
this.render(hbs`{{my-component}}`);
this.run(() => {
this.$('button').click();
});return wait().then(() => {
assert.ok(`some async thing is complete`);
});
This now becomes
import { click } from 'ember-native-dom-helpers';// ...this.render(hbs`{{my-component}}`);
await click('button');
assert.ok(`some async thing is complete`);
Much more succinct.
[Update] At the time of writing, many of the helpers were already added to the official ember-test-helpers which is included in all Ember apps. So if you upgrade to the latest version, you can do import { click } from "@ember/test-helpers";
. Eventually Ember Native DOM Helpers will be deprecated, but at the time of writing there are still some useful helpers there that haven’t yet been migrated, for example scrollTo()
and selectFiles()
.
Using the addon was easy. Install it by running ember install ember-native-dom-helpers
. You’ll also need to runember install ember-maybe-import-regenerator
if you want to use async/await in browsers which do not have native support. If you only want to use async/await
in your test code and not your app code, you can run ember install ember-maybe-import-regenerator-for-testing
.
There’s a handy codemod using jscodeshift to automatically migrate your tests. One of the things I love about the Ember community is that when syntax changes, we try to make the migration as automated as possible. This codemod will catch 99% of changes, but there were a few other changes I made manually:
- If you test for element presence using
Array#length
, for example in acceptance testsassert.ok(find('blah').length)
, or in integration tests,assert.ok(this.$('blah').length)
, the codemod will convert them both toassert.ok(findAll('blah').length)
. However this can be further simplified toassert.ok(find('blah'))
. This is because by defaultfind()
uses thedocument.querySelector
API to find a single match, andfindAll
usesdocument.querySelectorAll
to return an array of all matches. - The codemod does its best to remove unnecessary calls to
wait()
but you might still have some left in your code that require manual review. - The new helpers are not compatible with PhantomJS, so if you are trying this on an older app that is still using it (which I did), you’ll want to migrate your app to use headless Chrome and/or Firefox. Which brings me to my next topic:
Headless Chrome & Firefox
Midway through 2017, Chrome 59 was released with headless browser support, and continuous integration (CI) services like Travis CI and CircleCI introduced support for running tests using Chrome instead of PhantomJS. Ember 3.0 will drop support for PhantomJS. If you run ember new
today, your app will be configured to run tests using Chrome both locally using Testem and as part of the default Travis CI config.
I recently tried to make some changes to an app that I hadn’t touched in a while and was still running PhantomJS. If you’re in the same boat, you’ll want to upgrade ASAP. Ember apps are using more and more features that are in all modern browsers, but not PhantomJS, which can cause those dreaded “only in PhantomJS” errors. Furthermore you’ll need to switch before you can upgrade to Ember 3.0 and take advantage of exciting changes such as Ember Native DOM Helpers (described above) and using Ember without JQuery (RFC and a great blog post by Miguel Camba).
For help replacing PhantomJS with Chrome, Ember Map has a great article, Travis CI has documentation, and if you use CircleCI, my colleague at 201 Created, Kevin Pfefferle, recently wrote an article about using CircleCI 2.0 with Ember that will help.
Headless Firefox
One of our helpful readers pointed out that you can (and should) also run your tests using headless Firefox. I tried it and it was easy to setup. Here is some documentation on setting up Firefox on Travis CI. And here’s my complete testem config:
// testem.js/* eslint-env node */
module.exports = {
test_page: 'tests/index.html?hidepassed',
disable_watching: true,
launch_in_ci: [
'Chrome',
'Firefox'
],
launch_in_dev: [
'Chrome'
],
browser_args: {
Chrome: {
mode: 'ci',
args: [
'--disable-gpu',
'--headless',
'--remote-debugging-port=0',
'--window-size=1440,900'
]
},
Firefox: {
mode: 'ci',
args: [
'-headless',
]
}
}
};
And the relevant section of my .travis.yml
file.
# .travis.ymllanguage: node_js
node_js:
- "6"# Many people have run into errors with headless Chrome without this
sudo: required# This is important for headless Firefox
env:
- MOZ_HEADLESS=1
addons:
chrome: stable
firefox: latest
Ember Test Selectors
We’ve been using ember-test-selectors from Simplabs on several projects and love it. You can add test-only selectors in your templates, for example
<h1 data-test-post-title data-test-resource-id="{{post.id}}">{{post.title}}</h1>
And assert their presence
assert.ok(find(`[data-test-resource-id="2"]))
This syntax works in either acceptance or integration tests if you are using Ember Native DOM Helpers as described above.
All data-test-*
attributes are stripped from production builds, so you can add as many as you like without adding any weight to your payloads.
An earlier version of ember-test-selectors
shipped with a test helper, so you would write the assertion above as assert.ok(find(testSelector('resource-id', '2')
. However they’ve decided to deprecate this helper as it’s actually more verbose and doesn’t add much value. I’ve used some other addons that do something similar with helpers but I tend to agree that selecting for data-test-*
attributes in the test directly is the best approach.
Thankfully, there’s another codemod, the test-selectors-codemod from Lorcan Coyle which will remove the test helper from your app if you’ve been using it. That’s another great use of codemods.
Conclusion
I’m really excited about these recent changes in Ember. With EmberConf 2018 coming up, there are other exciting changes that are on the horizon. We’re also grateful to all the people who have been working on these addons and the codemods that make them easy to use. Some other useful codemods that I’ve seen are:
- Ember Modules Codemod to migrate apps to use modules import syntax.
- ES5 Getter Ember Codemod to migrate your apps away from calling
this.get()
for a computed property (support for which is added in Ember 3.1). This is by 201 Created’s own Jonathan Jackson, co-creator of the great podcast Ember Weekend.
201 Created loves helping our clients get the most out of Ember.js. We’re also hiring! So if you need help with testing or any other aspect of building Ember applications, or you’d like to come work with us, please say hello.