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

Testing and Ember

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Lindsey 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.

Avatar for Lindsey

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, {}, ""]; }); });