Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing and Ember

Lindsey
October 14, 2014

Testing and Ember

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.

Lindsey

October 14, 2014
Tweet

More Decks by Lindsey

Other Decks in Programming

Transcript

  1. • An ad hoc Backbone framework... • … with ad

    hoc tests • Most of which were actually testing the application • A few of which were end-to-end using Selenium and Capybara Where we’ve come from...
  2. … where we (re)started... • A well tested, purpose built

    framework in Ember • Immediate focus on application unit tests • Immediate friction
  3. … where we are now • When you feel friction

    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
  4. moduleFor(‘controller:foo’, { needs: [‘controller:bar’] }); test(‘a test’, function () {

    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?
  5. isolatedContainer container.register(fullName, resolver.resolve(normalizedFullName)); • Ember’s dependency injection, in your tests

    • 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
  6. Returning promises from unit tests • A helpful shortcut, but

    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) }); });
  7. Returning promises from unit tests stop(); Ember.RSVP.Promise.cast(result)['catch'](failTestOnPromiseRejection)['finally'](start); • The test

    runner is paused • The result of the test callback is resolved as a promise • Rejections fail the test • Finally, the test runner is resumed
  8. Unit testing observations • When do we use Ember.run? •

    “Needs sprawl” • Testing views needs: ['controller:group', 'controller:groups', 'controller:rules-editable', 'controller:variables', 'controller:classes', 'controller:rules-pinned', 'controller:rules-facts', 'controller:nodes', 'controller:ajax-error', 'controller:group-rules', 'controller:group-activity'],
  9. • Test helpers emulate user interaction • User interaction can

    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)
  10. The promise chain • Top level promise created by the

    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 () { /* … */ });
  11. // If this is the first async promise, kick off

    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?
  12. Death by a thousand debounces Executed 334 of 334 SUCCESS

    (38.395 secs / 37.751 secs) Executed 334 of 334 SUCCESS (3 mins 15.294 secs / 3 mins 14.649 secs) • Ember 1.6-beta • Ember 1.6.0
  13. Death by a thousand debounces hasTimers: function() { - return

    !!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?
  14. /* … preserves async behaviour but forces it to be

    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? • ...
  15. tick: function () { this._tick = Em.run.later(this, function () {

    /* 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.
  16. Heisenfocus if ($(document.activeElement).is($el)) { ok(true); } • Initial tests for

    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
  17. Sorting // ... Chrome and Phantom use different sorts for

    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
  18. Fixtures • ember-model, boon and bane • Sinon.JS… just bane

    • Pretender... just boon? var server = new Pretender(function(){ this.put('/api/songs/99', function(request){ return [404, {}, ""]; }); });