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

Testing your Backbone from the outside-in

Testing your Backbone from the outside-in

In the world of dynamic, asynchronous, real-time client-server applications, testing is hard. Fortunately, we already have the tools available to create full suites of acceptance, integration and unit tests, and use behaviour-driven design (BDD) methodologies to tease out a working implementation from the starting point of users' needs.

Jim Newbery

June 27, 2012
Tweet

More Decks by Jim Newbery

Other Decks in Technology

Transcript

  1. A user story As an efficient developer, I would like

    to save a snippet, So that I can retrieve it later
  2. A BDD scenario Given I have no snippets yet When

    I fill out and submit the snippet form Then I should see a messa e sayin my save was successful
  3. Snippetron Create a snippet Snippet title My first snippet Snippet

    contents var  a  =  5  *  5; Create snippet
  4. Useful acceptance testin features back()  &  forward() click() debugHTML() fill()

    getGlobal() mouseEvent() userAgent() viewport() capture() waitForSelector() waitForResource() waitUntilVisible()
  5. var  baseUrl  =  "http://localhost:3000"; casper.test.comment("Scenario:  Create  a  snippet"); casper.test.comment("Given  I

     am  on  the  home   page"); casper.start(baseUrl,  function()  {    this.test.comment("When  I  fill  out  the  form");    this.fill("form.create-­‐snippet",  {        "title":  "Alert  example",        "content":  "alert('Hello  World!');"    });    this.test.comment("And  I  submit  the  form");    this.click("input[type='submit']"); }); ...
  6. ... casper.test.comment("Then  I  should  see  a  message"); casper.waitUntilVisible(".message",  function()  {

       this.test.assertTextExists("Snippet  'Alert  example'  saved!"); },  function()  {    this.debugHTML();    this.test.fail("Create  snippet  message  timed  out"); },  3000); casper.run(function()  {    this.test.done(); });
  7. Cucumber.js feature Feature:  Saving  a  snippet    As  an  efficient

     developer    I  would  like  to  save  a  snippet    So  that  I  can  retrieve  it  later
  8. Cucumber.js scenario Scenario:  Successful  save    Given  I  am  on

     the  home  page    When  I  enter  "My  Snippet"  in  the  "Snippet  title"  field    And  I  enter  "Snippet  content"  in  the  "Snippet  content"  field    And  I  click  the  "Create  snippet"  button    Then  I  should  see  a  message  with  "Snippet  'My  Snippet'  saved!"
  9. Cucumber.js step definitions this.Given(/^I  am  on  the  home  page$/,  function

    (callback)  {    this.visit("/",  callback); }); this.When(/^I  enter  "([^"]*)"  in  the  "([^"]*)"  field$/,   function(val,  fieldSelector,  callback)  {    this.browser.fill(fieldSelector,  val,  callback); });
  10. Cucumber.js step definitions this.Then(/^I  should  see  a  message  with  "([^"]*)"$/,

       function(messageText,  callback)  {    if  (this.browser.text(".message")  !==  messageText)  {        callback.fail(new  Error("Expected  to  see  a  message  with   text  '"  +  messageText  +  '"'));    }  else  {        callback();    } });
  11. What unit to work on? A failin acceptance test can

    provide a clue CasperJS Cucumber.js
  12. Plannin & Communication submit handler Form view Snippet model creates

    + saves Server POST request show messa e success handler response submit user
  13. Many many tools • QUnit • Jasmine BDD • Mocha

    • Node.js test frameworks • Countless others
  14. BusterJS features • Multiple-browser testin • TDD or BDD syntax

    styles • inte rated test doubles: spies, stubs & mocks
  15. Why use test doubles? • Isolate subject of test •

    Ensure determinism • Protect from slow, inconsistent or incomplete dependencies
  16. SinonJS – a BDDer’s best friend • Send objects on

    spyin missions • Stub method responses • Stand-ins for server interactions • Manipulate space-time* *Well, time
  17. describe("Snippet.Model",  function()  {    describe("when  saved",  function()  {    

       beforeEach(function()  {            this.server  =  sinon.fakeServer.create();                        this.attrs  =  {                title:  "My  Snippet",                content:  "My  Snippet  content"            };            this.subject  =  new  Snippet.Model();            this.subject.save(this.attrs);        });        afterEach(function()  {            this.server.restore();        }); ...
  18. ...        it("makes  a  request  to  the  root",

     function()  {            expect(this.server.requests[0].url).toEqual("/");        });        it("submits  the  correct  body",  function()  {            var  body  =  JSON.parse(this.server.requests [0].requestBody);            expect(body.title).toEqual(this.attrs.title);            expect(body.content).toEqual(this.attrs.content);        });    }); });
  19. var  Snippet  =  {    Model:  Backbone.Model.extend({  urlRoot:  "/"  }),

       Views:  {        Form:  Backbone.View.extend({            events:  {                "submit":  "create"            },            create:  function(ev)  {                ev.preventDefault();                this.model  =  new  Snippet.Model();                this.model.save({                    "title":  this.$("[name='title']").val(),                    "content":  this.$("[name='content']").val()                });            }        })    } };
  20. What’s next? • Next acceptance test? • Exceptional circumstances •

    e. . validation, double submit • Exploratory testin