Backbone Testing — Testing Strategies and Tools
In this tutorial, you will learn about Backbone Testing. We cover key concepts, practical examples, and best practices to help you master this topic.
Testing Backbone applications requires strategies for Models, Collections, Views, Routers, and async operations. Using Mocha, Chai, and Sinon, you can test each component in isolation and verify behavior through assertions, spies, and stubs.
What You'll Learn
You'll learn how to write unit tests for Backbone components, mock server responses, test event-driven behavior, and integrate testing into your workflow.
Why It Matters
Backbone's loose coupling makes it testable, but only if you write tests. Untested Backbone apps accumulate event-binding bugs, silent data corruption, and regression errors that are hard to trace.
Real-World Use
A security analytics platform maintains 90% test coverage on Backbone modules. Each Model, Collection, and View has unit tests. When a new data source is added, tests verify Parsing, validation, and rendering before deployment.
flowchart LR
A[Test Suite] --> B[Model Tests]
A --> C[Collection Tests]
A --> D[View Tests]
A --> E[Router Tests]
B --> F[Sinon Spies]
C --> G[Mock Server]
D --> H[DOM Fixtures]
Setting Up the Test Environment
// test/setup.js — using Mocha + Chai + Sinon
// Install: npm install mocha chai sinon --save-dev
var expect = chai.expect;
var sinon = require('sinon');
var Backbone = require('backbone');
var $ = require('jquery');
Backbone.$ = $;
Testing Models
Model tests verify defaults, validation, custom methods, and event behavior.
var Task = Backbone.Model.extend({
defaults: { title: '', completed: false },
validate: function(attrs) {
if (!attrs.title) return 'Title required';
},
toggle: function() {
this.set('completed', !this.get('completed'));
}
});
// Tests
describe('Task Model', function() {
it('should have default values', function() {
var task = new Task();
expect(task.get('title')).to.equal('');
expect(task.get('completed')).to.be.false;
});
it('should validate title is required', function() {
var task = new Task();
var isValid = task.isValid();
expect(isValid).to.be.false;
});
it('should set attributes via constructor', function() {
var task = new Task({ title: 'Test task' });
expect(task.get('title')).to.equal('Test task');
});
it('should fire change event on set', function() {
var task = new Task({ title: 'Original' });
var spy = sinon.spy();
task.on('change:title', spy);
task.set('title', 'Updated');
expect(spy.calledOnce).to.be.true;
});
it('should toggle completed state', function() {
var task = new Task({ completed: false });
task.toggle();
expect(task.get('completed')).to.be.true;
task.toggle();
expect(task.get('completed')).to.be.false;
});
});
Testing Collections
Collection tests verify ordering, filtering, and event behavior.
var Task = Backbone.Model.extend({ defaults: { title: '', priority: 0 } });
var TaskList = Backbone.Collection.extend({
model: Task,
comparator: 'priority'
});
describe('TaskList Collection', function() {
it('should maintain sort order by priority', function() {
var tasks = new TaskList([
{ title: 'Medium', priority: 2 },
{ title: 'Low', priority: 1 },
{ title: 'High', priority: 3 }
]);
expect(tasks.pluck('title')).to.deep.equal(['Low', 'Medium', 'High']);
});
it('should fire add event', function() {
var tasks = new TaskList();
var spy = sinon.spy();
tasks.on('add', spy);
tasks.add({ title: 'New' });
expect(spy.calledOnce).to.be.true;
});
it('should filter by attribute', function() {
var tasks = new TaskList([
{ title: 'A', priority: 3 },
{ title: 'B', priority: 1 }
]);
var high = tasks.where({ priority: 3 });
expect(high.length).to.equal(1);
expect(high[0].get('title')).to.equal('A');
});
});
Testing Views with DOM Fixtures
View tests require a DOM fixture to render into.
var TaskView = Backbone.View.extend({
tagName: 'li',
events: {
'click .toggle': 'onToggle',
'click .delete': 'onDelete'
},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
},
render: function() {
this.$el.html(
'<span class="toggle">' + this.model.get('title') + '</span>' +
'<button class="delete">X</button>'
);
return this;
},
onToggle: function() {
this.model.set('completed', !this.model.get('completed'));
},
onDelete: function() {
this.model.destroy();
this.remove();
}
});
describe('TaskView', function() {
beforeEach(function() {
this.model = new Task({ title: 'Test' });
this.view = new TaskView({ model: this.model });
this.view.render();
$('#fixtures').append(this.view.el);
});
afterEach(function() {
this.view.remove();
$('#fixtures').empty();
});
it('should render the title', function() {
expect(this.view.$el.text()).to.contain('Test');
});
it('should toggle model on click', function() {
this.view.$('.toggle').click();
expect(this.model.get('completed')).to.be.true;
});
it('should re-render on model change', function() {
var spy = sinon.spy(this.view, 'render');
this.model.set('completed', true);
expect(spy.calledOnce).to.be.true;
});
it('should remove element on delete', function() {
this.view.$('.delete').click();
expect($('#fixtures').children().length).to.equal(0);
});
});
Testing Async Operations with Stubs
Stub Backbone.sync to test async behavior without a server.
describe('Async Model Operations', function() {
beforeEach(function() {
this.server = sinon.fakeServer.create();
this.server.respondWith(
'GET', '/api/tasks/1',
[200, { 'Content-Type': 'application/json' },
JSON.stringify({ id: 1, title: 'Stubbed task' })]
);
});
afterEach(function() {
this.server.restore();
});
it('should fetch model from server', function(done) {
var Task = Backbone.Model.extend({ urlRoot: '/api/tasks' });
var task = new Task({ id: 1 });
task.fetch({
success: function() {
expect(task.get('title')).to.equal('Stubbed task');
done();
}
});
this.server.respond();
});
it('should create model via save', function(done) {
this.server.respondWith(
'POST', '/api/tasks',
[201, { 'Content-Type': 'application/json' },
JSON.stringify({ id: 42, title: 'Created' })]
);
var Task = Backbone.Model.extend({ urlRoot: '/api/tasks' });
var task = new Task({ title: 'Created' });
task.save(null, {
success: function() {
expect(task.id).to.equal(42);
done();
}
});
this.server.respond();
});
});
Testing Routers
Verify that URL changes trigger the correct handlers.
describe('Router', function() {
beforeEach(function() {
this.router = new (Backbone.Router.extend({
routes: {
'': 'home',
'tasks/:id': 'showTask'
},
home: function() {},
showTask: function() {}
}))();
this.routerSpy = sinon.spy(this.router, 'showTask');
Backbone.history.start({ silent: true });
});
afterEach(function() {
Backbone.history.stop();
});
it('should route to showTask', function() {
this.router.navigate('tasks/42', { trigger: true });
expect(this.routerSpy.calledWith('42')).to.be.true;
});
});
Common Mistakes
- Not cleaning up DOM fixtures between tests. Leftover DOM elements cause test pollution. Use
beforeEach/afterEachto set up and tear down fixtures. - Testing implementation instead of behavior. Test that clicking a toggle button changes the model, not that a specific method was called. Behavior tests survive Refactoring.
- Forgetting to call
server.respond()in sinon tests. The fake server queues responses. Nothing happens until you callserver.respond(). - Not testing event cleanup. Verify that
stopListening()removes listeners. Orphaned listeners cause test pollution across tests. - Testing async code synchronously. Always use
donecallbacks or return promises in async tests. Mocha will timeout if you forget.
Practice Questions
- How do you test that a Model's validation works correctly?
- What is the purpose of
sinon.fakeServerin Backbone testing? - Why should views be tested with DOM fixtures?
- How do you verify that a Router navigates correctly?
- Challenge: Write a complete test suite for a Backbone application with at least one Model, Collection, View, and Router. Achieve 90% coverage using Mocha, Chai, and Sinon.
FAQ
Mini Project
Write a complete test suite for a Note application. Test Note Model (defaults, validation), Notes Collection (sorting, filtering), NoteView (rendering, toggle, delete), and AppRouter (navigation). Use Sinon to stub Backbone.sync. Achieve 90% line coverage.
What's Next
Now that you understand testing, learn Backbone Debugging for debugging techniques. Then build a complete application in Backbone Project.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro