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

Writing tests for Ember apps: Enjoying the experience

Writing tests for Ember apps: Enjoying the experience

Developers often complain that testing is “a necessary evil”. They complain even more about client-side testing. Writing and maintaining a suite of client-side tests does not need to be painful. In this talk, we’ll discuss why testing is *not* evil, and is, in fact, awesome. I’ll share what I’ve learned that has made my experience with EmberJS testing a pleasant and productive one.

Katie Gengler

June 17, 2014
Tweet

More Decks by Katie Gengler

Other Decks in Programming

Transcript

  1. Enjoying the experience of writing tests for Ember Apps Katie

    Gengler | Wicked Good Ember | June 17, 2014
  2. test("Values entered on the form are used”, function(){ ! openForm();

    ! fillIn(‘input.name’, 'Jane Doe'); click('a:contains(Update)'); ! andThen(function(){ assertContains('.template', 'Jane Doe'); }); ! }); Brittle & Coupled to the DOM
  3. test("Values entered on the form are used”, function(){ ! openForm();

    ! fillIn(‘.spec-name-input’, 'Jane Doe'); click(‘.spec-update-name-btn’); ! andThen(function(){ assertContains(‘.spec-name-display‘, 'Jane Doe'); }); ! }); ‘.spec-‘ classes used only for testing decouples your tests from the specifics of the DOM used to represent your app
  4. test("displays the outstanding balance", function(){ ! mockIndexResponse(); visit("/"); ! andThen(function(){

    assertContains('.spec-outstanding-balance', '$439'); }); ! }); mockIndexResponse() uses a JSON response that is generated by my API. The assertions made in tests using it are coupled to the data that is generated in that response.
  5. test("displays the outstanding balance", function(){ ! create(‘organization’, {outstandingBalance: 439.32}); visit("/");

    ! andThen(function(){ assertContains('.spec-outstanding-balance', '$439'); }); ! }); The ‘create’ helper makes objects directly in the store, this test is still coupled to data, but that data is setup right in this test.
  6. test("Customer list displays all customers, updates with customer added”, function(){

    make(‘organization’, {customers: []}); visit('/customers'); ! andThen(function(){ assertContains('.spec-customer-results', 'No matching customers found.’) }); ! ! make('customer'); make('customer'); ! ! andThen(function(){ assertContains('.spec-customer-results', 'Displaying 2 matching customers'); }); }); ‘make’ is just a plain function. This test will be ‘flaky’ because the make helper is not asynchronous-aware, it will not wait for previous async-helpers to run before it runs.
  7. test("Customer list displays all customers, updates with customer added”, function(){

    make(‘organization’, {customers: []}); visit('/customers'); ! andThen(function(){ assertContains('.spec-customer-results', 'No matching customers found.’) }); ! andThen(function(){ make('customer'); make('customer'); }); ! andThen(function(){ assertContains('.spec-customer-results', 'Displaying 2 matching customers'); }); }); The andThen helper will not call the function passed to it until the previous async-aware calls in the test are completed. This ensures that the two customers will not be created until after the assertion that there are no customers.
  8. Ember.Handlebars.helper('pluralize', function() { var values = Array.prototype.slice.call(arguments, 0, -1); var

    count = values[0]; var singular = values[1]; var plural = values[2]; ! if( plural == null ) { plural = singular + 's'; } ! if( count === 1 ) { return '1 ' + singular; } return count + ' ' + plural; });
  9. Ember.Handlebars.helper('pluralize', function() { var values = Array.prototype.slice.call(arguments, 0, -1); var

    count = values[0]; var singular = values[1]; var plural = values[2]; ! if( plural == null ) { plural = singular + 'e'; } ! if( count === 1 ) { return '1 ' + singular; } return count + ' ' + plural; });
  10. Ember.Test.registerHelper('create', function(app, objType, properties) { var props; Ember.run(function(){ props =

    $.extend({id: createGuid()}, properties); store().push(objType, props); }); return store().getById(objType, props.id); ! });
  11. Ember.Test.registerHelper('make', function(app, objType, properties){ ! var args = Array.prototype.slice.call(arguments, 2);

    var func = createFuncs[objType]; if(func){ return createFuncs[objType].apply(this, args); } return create(objType, properties); ! });
  12. function createOrganization(properties){ var org = store().getById('organization', 'current'); if(org) { return

    org; } var defaults = { id: 'current', name: 'John Doe Consulting' }; var orgProps = $.extend(defaults, properties); return create('organization', orgProps); }
  13. test("Edit state resets between documents", function(){ var customer = make(‘customer’);

    openCreateDocument(customer); ! click(‘.spec-document-action:contains(Edit)’); ! andThen(function(){ assertExists(‘.spec-document-action:contains(Done)’, 'Done button should show'); }); ! mockDocumentCreate(customer); click('.spec-save-document'); click('.spec-return-to-customer'); ! openCreateDocument(customer); ! andThen(function(){ assertExists(‘.spec-document-action:contains(Edit)’, 'Edit button should show --- edit state should be reset'); }); ! });