Baby steps to Backbone.js: Unit testing of models
Unit testing is important part of development process. If you care about the application state and code quality, sooner or later you start to automate the tests. My experience shows, sooner is better option. There are several approaches to unit testing, `test-first` and `test-after`. There are many holy wars appears on that topic. I would say, both options works - but `test-first` or test driven development, works better for me.
By the end of the day, it’s only important that tests exists and helping to catch regression bugs. Still, developing by `test-first` helps to see the problem before ahead and in general provides better code quality (that would conclude the holy war).
Today we going to write some tests, that would cover our existing model class Feedback.js
. Since the code is already written, we will go `test-after` approach. Fortunately, the code is quite simple, so it would not make a problem to unit test that. But before, let’s setup our testing infrastructure.
Folders and files
We’ll have a new folder called `spec`. Inside the spec folder, I’ll have `models` and `views` folders that would contain tests for models and views.
Tests runner
I’ll be using Jasmine test framework. It’s very easy to setup it, what we need is jasmine.js
and jasmine.css
to be placed on proper folders and setup a test page. Test page is a simple html file, which will be entry point for our testing. If you download jasmine standalone bundle you will see SpecRunner.html
inside. It could be easily tailored for custom needs.
In the head part of tests.html
we need to reference all required .css and .js files.
<title>Feedback form specs</title>
<link rel="stylesheet" type="text/css" href="content/jasmine.css">
<script type="text/javascript" src="/scripts/libs/jquery-1.7.2.js"></script>
<script type="text/javascript" src="/scripts/libs/underscore.js"></script>
<script type="text/javascript" src="/scripts/libs/backbone.js"></script>
<script type="text/javascript" src="/scripts/libs/jasmine.js"></script>
<script type="text/javascript" src="/scripts/libs/jasmine-html.js"></script>
<script type="text/javascript" src="/scripts/libs/mock-ajax.js"></script>
<!-- Sources -->
<script type="text/javascript" src="/scripts/src/models/Feedback.js"></script>
<!-- Specs -->
<script type="text/javascript" src="/scripts/spec/models/Feedback.spec.js"></script>
Jasmine tests in essence
Testing with Jasmine is fun and easy. Jasmine is BDD-style framework, so if you practiced TDD with another frameworks, the style might confuse initially. Let’s review the Jasmine test skeleton.
describe('Jasmine spec', function () {
var value;
beforeEach(function () {
value = 1;
});
it ('should fail', function () {
expect(value).toBe(0);
});
describe('when value is changed', function () {
beforeEach(function () {
value = 0;
});
it ('should pass', function () {
expect(value).toBe(0);
})
});
});
In this example, value
is our SUT (System under test). beforeEach()
function is `context-establish` function, where SUT is initialized (in TDD it’s both arrange/act). it
function is assert part. Here, we set our expectations about which state should SUT be in to. Notice, that beforeEach
are nested into describe
, so you tweek SUT depending on case.
Writing some tests
The only one functionality that Feedback.js
model contains currently is validation. Let’s test that.
describe('Feedback.js spec', function () {
var model;
beforeEach(function () {
model = new Feedback();
});
describe('when model is validating', function () {
var errors;
});
});
This is something to start with. It does not do any asserts, so now we’ll add some real cases. First case, is than both `email` and `feedback` attributes are absent.
describe('when email and feedback fields are absent', function () {
beforeEach(function () {
errors = model.validate({});
});
it ('should have 2 errors', function () {
expect(errors.length).toBe(2);
});
it ('should have email fields as invalid', function () {
expect(errors[0].name).toBe('email');
});
it ('should have feedback field as invalid', function () {
expect(errors[1].name).toBe('feedback');
});
});
It’s is possible that user put email, but forgot about feedback.
describe('when email is set, but feedback is absent', function () {
beforeEach(function () {
errors = model.validate({ email: 'a@a.com'});
});
it ('should have 1 error', function () {
expect(errors.length).toBe(1);
});
it ('should have feedback field as invalid', function () {
expect(errors[0].name).toBe('feedback');
});
it ('should have error message', function () {
expect(errors[0].message).toBeDefined();
});
});
Moving on, user might put feedback but forgot about email.
describe('when feedback is set, but email is absent', function () {
beforeEach(function () {
errors = model.validate({ feedback: 'TDD is awesome'});
});
it ('should have 1 error', function () {
expect(errors.length).toBe(1);
});
it ('should have email field as invalid', function () {
expect(errors[0].name).toBe('email');
});
it ('should have error message', function () {
expect(errors[0].message).toBeDefined();
});
});
Tests report
If you now try to run test.html
in browser, you will something like that.
Conclusions
Testing of Backbone.Model’s is pretty simple thing. It’s nothing more than testing some business logic, that might reside inside. Testing of views is a bit trickier thing, but we will see how to do that next time.
Unit testing is important part of development process. If you care about the application state and code quality, sooner or later you start to automate the tests. My experience shows, sooner is better option. There are several approaches to unit testing, `test-first` and `test-after`. There are many holy wars appears on that topic. I would say, both options works - but `test-first` or test driven development, works better for me.
By the end of the day, it’s only important that tests exists and helping to catch regression bugs. Still, developing by `test-first` helps to see the problem before ahead and in general provides better code quality (that would conclude the holy war).
Today we going to write some tests, that would cover our existing model class Feedback.js
. Since the code is already written, we will go `test-after` approach. Fortunately, the code is quite simple, so it would not make a problem to unit test that. But before, let’s setup our testing infrastructure.
Folders and files
We’ll have a new folder called `spec`. Inside the spec folder, I’ll have `models` and `views` folders that would contain tests for models and views.
Tests runner
I’ll be using Jasmine test framework. It’s very easy to setup it, what we need is jasmine.js
and jasmine.css
to be placed on proper folders and setup a test page. Test page is a simple html file, which will be entry point for our testing. If you download jasmine standalone bundle you will see SpecRunner.html
inside. It could be easily tailored for custom needs.
In the head part of tests.html
we need to reference all required .css and .js files.
<title>Feedback form specs</title> <link rel="stylesheet" type="text/css" href="content/jasmine.css"> <script type="text/javascript" src="/scripts/libs/jquery-1.7.2.js"></script> <script type="text/javascript" src="/scripts/libs/underscore.js"></script> <script type="text/javascript" src="/scripts/libs/backbone.js"></script> <script type="text/javascript" src="/scripts/libs/jasmine.js"></script> <script type="text/javascript" src="/scripts/libs/jasmine-html.js"></script> <script type="text/javascript" src="/scripts/libs/mock-ajax.js"></script> <!-- Sources --> <script type="text/javascript" src="/scripts/src/models/Feedback.js"></script> <!-- Specs --> <script type="text/javascript" src="/scripts/spec/models/Feedback.spec.js"></script>
Jasmine tests in essence
Testing with Jasmine is fun and easy. Jasmine is BDD-style framework, so if you practiced TDD with another frameworks, the style might confuse initially. Let’s review the Jasmine test skeleton.
describe('Jasmine spec', function () { var value; beforeEach(function () { value = 1; }); it ('should fail', function () { expect(value).toBe(0); }); describe('when value is changed', function () { beforeEach(function () { value = 0; }); it ('should pass', function () { expect(value).toBe(0); }) }); });
In this example, value
is our SUT (System under test). beforeEach()
function is `context-establish` function, where SUT is initialized (in TDD it’s both arrange/act). it
function is assert part. Here, we set our expectations about which state should SUT be in to. Notice, that beforeEach
are nested into describe
, so you tweek SUT depending on case.
Writing some tests
The only one functionality that Feedback.js
model contains currently is validation. Let’s test that.
describe('Feedback.js spec', function () { var model; beforeEach(function () { model = new Feedback(); }); describe('when model is validating', function () { var errors; }); });
This is something to start with. It does not do any asserts, so now we’ll add some real cases. First case, is than both `email` and `feedback` attributes are absent.
describe('when email and feedback fields are absent', function () { beforeEach(function () { errors = model.validate({}); }); it ('should have 2 errors', function () { expect(errors.length).toBe(2); }); it ('should have email fields as invalid', function () { expect(errors[0].name).toBe('email'); }); it ('should have feedback field as invalid', function () { expect(errors[1].name).toBe('feedback'); }); });
It’s is possible that user put email, but forgot about feedback.
describe('when email is set, but feedback is absent', function () { beforeEach(function () { errors = model.validate({ email: 'a@a.com'}); }); it ('should have 1 error', function () { expect(errors.length).toBe(1); }); it ('should have feedback field as invalid', function () { expect(errors[0].name).toBe('feedback'); }); it ('should have error message', function () { expect(errors[0].message).toBeDefined(); }); });
Moving on, user might put feedback but forgot about email.
describe('when feedback is set, but email is absent', function () { beforeEach(function () { errors = model.validate({ feedback: 'TDD is awesome'}); }); it ('should have 1 error', function () { expect(errors.length).toBe(1); }); it ('should have email field as invalid', function () { expect(errors[0].name).toBe('email'); }); it ('should have error message', function () { expect(errors[0].message).toBeDefined(); }); });
Tests report
If you now try to run test.html
in browser, you will something like that.
Conclusions
Testing of Backbone.Model’s is pretty simple thing. It’s nothing more than testing some business logic, that might reside inside. Testing of views is a bit trickier thing, but we will see how to do that next time.