Testing and supporting Backbone.js views

Having now created test suites for Backbone.js models and collections, we turn to expanding our test coverage to a Backbone.js view.

The Notes application single note view

The first Backbone.js view we will examine is App.Views.NoteView. This view is responsible for rendering App.Models.Note Markdown data into full HTML as shown in the following screenshot:

The Notes application single note view

View rendered Markdown

The underlying model data for the figure includes the following attributes:

  • title:
    My Title
  • text:
    ## My Heading
    * List item 1
    * List item 2

The text attribute data transforms to HTML as:

<h2 id="myheading">My Heading</h2>
<ul>
  <li>List item 1</li>
  <li>List item 2</li>
</ul>

App.Views.NoteView is responsible for performing this conversion. The notes/app/js/app/views/note-view.js file first provides an initialize function, which sets model listeners to re-render or clean up the view and then kicks off render(). The render function uses the Showdown library to convert the model text Markdown data into HTML and then passes the finished data to the view template:

App.Views.NoteView = Backbone.View.extend({

  template: _.template(App.Templates["template-note-view"]),

  converter: new Showdown.converter(),

  initialize: function () {
    this.listenTo(this.model, "change", this.render);
    this.listenTo(this.model, "destroy", this.remove);
    this.render();
  },

  // Convert note data into Markdown.
  render: function () {
    this.$el.html(this.template({
      title: this.model.get("title"),
      text: this.converter.makeHtml(this.model.get("text"))
    }));
    return this;
  }
});

This view includes an Underscore.js template (App.Templates["template-note-view"] in notes/app/js/app/templates/templates.js), which inserts the title and text data into HTML:

App.Templates["template-note-view"] =
  "<div class="well well-small">" +
  "  <h2 id="pane-title"><%= title %></h2>" +
  "</div>" +
  "<div id="pane-text"><%= text %></div>";

Rendering the model data into an HTML form with App.Views.NoteView.render() gives us the following resulting HTML:

<div class="well well-small">
  <h2 id="pane-title">My Title</h2>
</div>
<div id="pane-text">
  <h2 id="myheading">My Heading</h2>
  <ul>
    <li>List item 1</li>
    <li>List item 2</li>
  </ul>
</div>

Now that we have introduced a simple view to work with, we will examine how to test its behavior.

Creating HTML fixtures for view tests

The Backbone.js application tests that we have written up to this point don't interface with the DOM or HTML of a web page. This simplifies our test environment because the application's web page (for example, index.html) is very different from our test driver page (for example, test.html). However, Backbone.js views nearly always involve a healthy amount of DOM interaction.

To this end, we need an HTML test fixture—one or more DOM elements in the test driver page that we can interact with and modify during tests. At the same time, we don't want the fixture HTML causing havoc in the test code of the driver page. Accordingly, we create a single, hidden div element in the chapters/03/test/test.html driver page for our application view tests:

<body>
  <div id="mocha"></div>

  <!-- Test Fixtures. -->
  <div id="fixtures"style="display: none; visibility: hidden;"></div>

Now, our tests can reference $("#fixtures") in jQuery and get access to the fixture container. The tests can then add elements as needed to exercise any desired view/DOM interaction.

Tip

Advanced HTML fixtures

We have scratched only the surface of HTML fixtures in this chapter. More sophisticated fixture schemes and libraries exist, with features such as sandboxing application HTML code within an iframe to avoid test code cross-pollination and loading HTML fixture code from external application files. Two promising management libraries that are compatible with Mocha are jsFixtures (https://github.com/kevindente/jsFixtures) and js-fixtures (https://github.com/badunk/js-fixtures).

Walking through the view test suite

Let's make our way through the code in chapters/03/test/js/spec/views/note-view.spec.js, which is the test suite for App.Views.NoteView. Recalling the Backbone.js view testing goals in Chapter 2, Creating a Backbone.js Application Test Plan, we will check if the view renders appropriate HTML using a model and template, binds HTML results to the expected DOM location, and interacts correctly with the application events.

Tip

Writing tests on your own for the examples

To make the narrative in this book flow better, we will follow a general scheme of presenting a Backbone.js application component and then walking through tests that illustrate a particular lesson, technique, or tool. Unfortunately, this is the reverse of the recommended Test-Driven Development process, which writes tests describing application behavior first, then writes the implementation, and iterates until the overall behavior is correct.

For your work with this book, we strongly encourage you to take a moment, before the book presents test examples to design and implement your own tests for the sample application components. After writing your own tests, you can move on to the book's examples to check your work and identify additional testing ideas and techniques.

The suite begins with a describe declaration and a setup/teardown code. At the commencement of the suite execution, a view fixture ($("<div id='note-view-fixture'></div>")) is created and stored in this.$fixture. Our setup for each test (beforeEach()/afterEach()) binds the new this.$fixture fixture to the HTML fixture holder $("#fixtures") and creates an App.Views.NoteView object with an App.Models.Note model. After all tests in the suite are done, the fixtures holder $("#fixtures") is emptied:

describe("App.Views.NoteView", function () {

  before(function () {
    // Create test fixture.
    this.$fixture = $("<div id='note-view-fixture'></div>");
  });

  beforeEach(function () {
    // Empty out and rebind the fixture for each run.
    this.$fixture.empty().appendTo($("#fixtures"));

    // New default model and view for each test.
    //
    // Creation calls `render()`, so in tests we have an
    // *already rendered* view.
    this.view = new App.Views.NoteView({
      el: this.$fixture,
      model: new App.Models.Note()
    });
  });

  afterEach(function () {
    // Destroying the model also destroys the view.
    this.view.model.destroy();
  });

  after(function () {
    // Remove all subfixtures after test suite finishes.
    $("#fixtures").empty();
  });

With these variables and DOM elements available, we can test whether a default model renders the expected HTML, using jQuery. Note that because its initialize function calls render, instantiating an App.Views.NoteView object adds the rendered HTML to our DOM fixture:

  it("can render an empty note", function () {
    var $title = $("#pane-title"),
      $text = $("#pane-text");

    // Default to empty title in `h2` tag.
    expect($title.text()).to.equal("");
    expect($title.prop("tagName").toLowerCase()).to.equal("h2");

    // Have simple default message.
    expect($text.text()).to.equal("Edit your note!");
    expect($text.html()).to.contain("<p><em>Edit your note!</em></p>");
  });

The second spec changes the model attributes title and text to render more complex HTML.

The tricky part is waiting until after the model listeners call render() and update the DOM to inspect the new HTML values. Our technique here is to observe that render() already listens to the model event change and add an additional one-time once() listener on this event to check the HTML.

However, note that this is a brittle way to handle the asynchronous nature of the test behavior. The rendering code could take more time to finish than our assertions, thereby breaking the test. A better solution is to wait on the render() function call to finish and then run the test code—a technique that we can more readily perform with Sinon.JS spies, stubs, and mocks, which are discussed in detail in the subsequent chapters:

  it("can render more complicated markdown", function (done) {
    this.view.model.once("change", function () {
      var $title = $("#pane-title"),
        $text = $("#pane-text");

      // Our new (changed) title.
      expect($title.text()).to.equal("My Title");

      // Rendered Markdown with headings, list.
      expect($text.html())
        .to.contain("My Heading</h2>").and
        .to.contain("<ul>").and
        .to.contain("<li>List item 2</li>");

      done();
    });

    // Make our note a little more complex.
    this.view.model.set({
      title: "My Title",
      text: "## My Heading
" +
            "* List item 1
" +
            "* List item 2"
    });
  });
});
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
44.222.82.133