This talk ran through Ember's testing utilities and ember-qunit to gain understanding on how they work. It also showed off some pain points that were run into when testing a large Ember application.
in Ember it’s nudging you towards a better way: integration tests! • They’re fast(er than Selenium) • They’re expressive • They’re less brittle • They’re integrated (with Ember) • They test actual workflows
var fooController = this.subject(); var barController = this.container.lookup(‘controller:bar’); }); A simple test, and some questions • Where do the controllers come from? • Do new ones get created every test run?
• Resolver finds the factory for “controller:bar” • Container registers the factory so we can create our objects under test • Our subject and its dependencies are resolved and added to the container
how do we get to that “ok()”? • What about when we *don’t* return a promise? test(‘a promise returning function’, function () { return pauseForASecond().then(function () { ok(true) }); });
include complicated behaviour • Helpers only continue when the application is in a synchronized state • How do they know when that happens? Integration testing helpers visit(url) fillIn(selector, text) click(selector) keyEvent(selector, type, keyCode) triggerEvent(selector, type, options) andThen(callback)
first helper • Each helper adds a promise to the chain • Starting the test starts the chain visit(“/blog”); click(“.post1”); andThen(function () { /* … */ }); click(“.post1 .expand-comments”); andThen(function () { /* … */ });
the async test // Every 10ms, poll for the async thing to have finished // 1. If the router is loading, keep polling // 2. If there are pending Ajax requests, keep polling // 3. If there are scheduled timers or we are inside of a run loop, keep polling // If this is the last async promise, end the async test // Synchronously resolve the promise Wait How does Ember know when to advance to the next test helper?
!!timers.length || autorun; + return !!timers.length || !!debouncees.length || !!throttlers.length || autorun; }, • A change in Backburner.js means “scheduled timers” now includes debounces • 3-4 minutes per test run is unacceptable, how do we get around this?
very quick. */ var originalDebounce = Ember.run.debounce; Ember.run.debounce = function (target, method, wait) { /* … */ wait = 1; /* … */ }); Death by a thousand debounces • Monkey patch the framework! • What could possibly go wrong? • ...
/* update properties */ this.tick(); }, 1000); }, start: function () { if (!Em.testing) { this.tick(); } } Infinite ticking clock A simple clock component that updates itself every second… … by repeatedly calling the “later” method… … causing an infinite loop of ticks.
focus had intermittent failures in browsers • Tracked down failures to our use of :focus jQuery selector • Turns out, focus is hard • jQuery was being (correctly) pedantic about determining what has focus
localeCompare. // This is fixed in Phantom2, remove when fixed. • We made a mistake and didn’t define how our applications sorted during design • Don’t do that • Once we started to care about sorting, we found inconsistencies around localeCompare