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.

0a3a4e125aba1e3e86d45ff6f708f203?s=128

Katie Gengler

June 17, 2014
Tweet

Transcript

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

    Gengler | Wicked Good Ember | June 17, 2014
  2. @katiegengler FundingGates

  3. Why do we write tests?

  4. Overcoming developer inertia

  5. Documenting behavior

  6. Preventing regressions

  7. What causes testing pain?

  8. Slow Expensive Brittle Flaky TESTS

  9. What is so awesome about ember-testing?

  10. I can thoroughly test my UI

  11. Ember integration tests are not slow.

  12. They are not expensive.

  13. They are not brittle … with a little effort

  14. Common cause of brittle tests: Coupling

  15. Coupling to the DOM

  16. 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
  17. 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
  18. Coupling to your data

  19. 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.
  20. 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.
  21. Ember integration tests are not flaky … with a little

    awareness
  22. Common cause of flaky tests: Asynchronous behavior

  23. 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.
  24. 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.
  25. I can thoroughly test my UI

  26. The testing pyramid UI Service UNIT

  27. Fewest UI tests “brittle, expensive, and time consuming” UI Service

    UNIT
  28. Testing economics

  29. 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; });
  30. How many tests should fail if I change pluralize?

  31. 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; });
  32. In my test suite, >10 tests fail when I break

    pluralize
  33. Tips & Tricks

  34. Data helpers

  35. 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); ! });
  36. 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); ! });
  37. 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); }
  38. function mockIndexResponse(){ mockRequest("get", "/api/organizations/current", 
 Responses.organization); } mockRequest uses mockjax

    Alternative: trek/pretender
  39. Testing things you might not normally think to test.

  40. 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'); }); ! });
  41. <3 Ember Testing

  42. Thanks! Questions? @katiegengler or katie@kmg.io