How we achieved green builds in an enterprise Ember.js app
We started using Ember about 3 years back. At that time Mocha seemed to be ahead of QUnit in terms of features. Also, it was similar to RSpec and so it made our transition much easier. Hence we chose Mocha as the testing framework. But a lot has changed since then.
QUnit not only caught up with Mocha but sprinted ahead.
Our codebase grew exponentially with thousands of test cases, slowly but steadily introducing flaky tests. At one point in time, it was very difficult to see a green build since some tests would almost always randomly fail.
Before things got out of hand, we brainstormed a solution to stabilise the test pipeline in the CI. Enter QUnit.
Performance: QUnit is not only easy, but fast. Opinionated and lean API, but extensible.
Ember-QUnit is the default unit testing helper suite for Ember which means new features are introduced in ember-qunit more rapidly than in ember-mocha.
Tests in QUnit run faster than mocha.
ember-exam not only has better integration with QUnit but also some additional features like
--load-balanceare available only with QUnit.
Testing issues such as async leakages and global mutations are better thrown by QUnit.
Mocha to QUnit migration
#STEP 1: Setup the testbed
Our testing codebase is huge. We had more than 5000 tests, with developers adding to that number every single day. So we had to find a way to seamlessly migrate the existing test cases. Hence, we wrote an in-repo addon (to be open-sourced soon!) which would enable both Mocha and QUnit tests to be run simultaneously in our CI.
#STEP 2: Build the tools
Ember community has a pretty good collection of codemods, but unfortunately, there wasn’t one to convert Mocha to QUnit (although the inverse was available).
Before we jumped into writing codemods, we wanted to analyse the existing test assertions which were written using Chai and obtain a list of unique
expect statements. So we wrote this tiny node JS tool:
Node CLI tool to extract chai assertions - expect statements from JS test files. Uses Recast under the hood to parse the JS files and filter expect statements Aggregates the entire list of expect variations Finds the list of unique expects.
Once we had the list of unique assertions in Chai, we started mapping the corresponding alternatives in QUnit assertions. However, QUnit has a very basic collection of assertions, especially when compared to something like chai. So we wrote this Ember addon to address the custom assertions:
QUnit has a very basic collection of assertions, especially when compared to something like chai. Let's take an example. If you want to check if a string/array/object is empty, you would do something like this in chai: Similar results can be achieved using assert.notOk in QUnit too, but you would have to do something like this: Wouldn't it be easier if there's a cleaner way to achieve the same results?
Finally we started writing the actual codemod transform which would automagically convert all the tests to QUnit —
mocha-to-qunit. During pilot runs, we observed that we had a lot of async leakages in our test suites resulting in this error:
Assertion failed: You have turned on testing mode, which disabled the run-loop’s autorun. You will need to wrap any code with asynchronous side-effects in an Ember.run
So we wrote another codemod transform to fix that —
A collection of codemods by Freshworks. To run a specific codemod from this project, you would run the following: npx @freshworks/ember-codemods path/of/files/ or/some/*glob.js # or yarn global add @freshworks/ember-codemods ember-codemods path/of/files/ or/some/*glob.js clone the repo change into the repo directory yarn
#STEP 3: Incremental migration
Thanks to the parallel testbed setup and the tooling, we could migrate the code module-by-module without any blockers or dependencies. We wrote a cookbook on “how to migrate from Mocha to QUnit” along with a “best practices guide for writing tests using QUnit” and shared it across the teams.
One month and 5000 test cases later, here we are, with more stable tests and green builds.