Testing the application: workflows

August 27, 2014

In addition to the model and view tests, tests for the workflow of the Backbone application need to be added.

As before, see the full repository for more context around the tests.

The test shell

As with the previous tests, the specification for the workflow needs to pull in a reference to the workflow module. Further, since testing the workflow will involve test that specific views are created and loaded, the views used by the workflow must be pulled in as well.

define(
 ["workflows/createClaim", "views/planSearch", "views/basicInformation", "views/issues", "views/confirmation"],
 function( CreateClaim, PlanSearchView, BasicInformationView, IssuesView, ConfirmationView ){
  describe("Workflow :: CreateClaim", function() {
    // ... Tests go here
  });
});

Testing step transitions

The workflow contained a few functions that can be tested without needing to bother with actual steps (with their own model and view dependencies).

workflowOrder: [
 // ...
],
initialize: function() {
  this.currentIndex = -1;
},
start: function(data) {
  this.next(data);
},
next: function(data) {
  this.currentIndex++;
  this.currentStep = this.workflowOrder[this.currentIndex];
  this[this.currentStep](data);
},

In order to test these functions without using the views and models of the actual application, the workflowOrder array can be replaced by different functions defined in the tests. Defining the functions specific to each test allows very targeted tests (was the step counter incremented, did the function get called, did the data get passed along, etc.).

describe("initialize", function() {
  it("should set the current index to -1", function() {
    var model = new CreateClaim();
    expect(model.currentIndex).toBe(-1);
  });
});

// ...

describe("next", function() {
  var workflow;
  beforeEach(function() {
    workflow = new CreateClaim();
  });
  
  it("should advance the step index", function() {
    workflow.workflowOrder = ["testingFirst", "testingSecond"];
    workflow.currentIndex = 0;
    workflow.testingSecond = function(data) {};
    
    workflow.next();
    
    expect(workflow.currentIndex).toBe(1);
  });
  
  it("should update the step", function() {
    workflow.workflowOrder = ["testingFirst", "testingSecond"];
    workflow.currentIndex = 0;
    workflow.testingSecond = function(data) {};
    
    workflow.next();
    
    expect(workflow.currentStep).toBe("testingSecond");
  });
  
  it("should call the step", function() {
    var called = false;
    workflow.workflowOrder = ["testingFirst", "testingSecond"];
    workflow.currentIndex = 0;
    workflow.testingSecond = function(data) {
      called = true;
    };
    
    workflow.next();
    
    expect(called).toBe(true);
  });
  
  it("should pass the data to the step", function() {
    var passedData = null;
    workflow.workflowOrder = ["testingFirst", "testingSecond"];
    workflow.currentIndex = 0;
    workflow.testingSecond = function(data) {
      passedData = data;
    };
    
    workflow.next(100);
    
    expect(passedData).toBe(100);
  });
});

Testing actual steps

The individual steps can be tested irrespective of the navigation components. For each step, a common set of tests could exist: the displayed view is updated, the view is rendered, the correct view is created, and the model is correctly built. Other tests may be useful depending on the step itself.

Since the workflow being tested isn’t actually being rendered on a page, setting the mainContent function of the workflow to a local variable (rather than the jQuery reference it uses in the application) causes the workflow to interact with and render into a variable scoped to the test.

describe("steps", function() {
  var workflow;
  var mainContent;
  beforeEach(function() {
    mainContent = $("<div/>");
    workflow = new CreateClaim();
    workflow.mainContent = function() {
      return mainContent;
    }
  });
  
  describe("planSearch", function() {
    var data;
    beforeEach(function() {
      data = {};
    });
    
    it("should update the view", function() {
      workflow.planSearch(data);
      expect(workflow.subview).not.toBe(null);
      expect(workflow.subview).not.toBe(undefined);
    });
    
    it("should render the view", function() {
      workflow.planSearch(data);
      expect(mainContent.html()).not.toBe("");
    });
    
    it("should create a view of the correct type", function() {
      workflow.planSearch(data);
      expect(workflow.subview instanceof PlanSearchView).toBe(true);
    });
    
    it("should pass options to the model", function() {
      workflow.planSearch(data);
      expect(workflow.subview.model.workflow).toBe(workflow);
    });
  });
});

This same style of tests is used for each of the steps in the workflow.