Writing reliable JavaScript code at scale is difficult. The language lacks built-in formal structures that enable reliable engineering practices. Fortunately establishing convention and selecting mature libraries goes a long way towards building a trustworthy architecture. Furthermore writing unit tests gains confidence in our applications.
In this first article in a series on unit testing JavaScript web apps I’ll show Jasmine for testing an object
inheriting from Backbone.Model
. In particular we’ll review simulating service
responses and
exercising accessor functions.
I wrote a sample app supporting this article by putting concepts into practice. If you want to get your hands on something and poke around feel free clone it from here:
https:// github.com/ KDawg/ JasmineTestingBackBoneModel
Once you pull down that code running tests means opening this file in Chrome:
JasmineTestingBackBoneModel/ tests/ SpecRunner.html
Jasmine’s unit test reporting looks like:
When you have a chance read through SpecRunner.html
looking for how it organizes
<script>
tags bringing in dependent JavaScript libraries, app-specific code, and Jasmine support. It’s
straight-forward with a few rules to abide success will be yours.
I won’t try training you on Jasmine to any extent. If you already know it then cool, but otherwise check out these docs for more domain-specific knowledge.
Pretending to have a service response is far quicker than hitting the real end-point. Jasmine
lets us short-circuit a Model.fetch()
by immediately returning an object imitating an
evaluated JSON response.
Is that important? It’s so important!
Slow tests create friction that programmers will route around. Avoiding running the test suite means tests aren’t maintained, new tests aren’t written, and submitted code is not automatically assured for passing regression.
Don’t get me wrong. So-called “integration tests” hitting the services with well-known user accounts and routes are totally worth coding. My warning is because they’re naturally slower so it’s worth figuring out how to segregate integration-tests into overnight batch jobs reported by your continuous-integration server.
Do you need a CI server? Have a look at Jenkins.
Great, now that you’re convinced that mocking out the service response makes unit tests respond more quickly, I’ll also claim that testing the models doesn’t require testing the service or the transmission mechanisms.
You’ll see the Jasmine unit tests are hierarchically organized into larger suites and specs blocked by subject, and those are broken into detailed expectations. Reading them in a cascading flow appears like revealing narrative.
A Coffee Model has property getting functions that should return the name when it fetches should be able to parse mocked service response
At this point you might be wondering “what’s up with the coffee thing?” This example test is written around the defining model for my site “Made Fresh Coffee” where people virtually mix tasty ingredients, or pull from a complete recipe list, creating a virtual coffee sent to their favorite people.
Let’s go through code snippets of the example project seeing how Jasmine helps us unit test application code, and why I decided to do things a certain way.
This is my convention kicking off a unit test assuring the simplest things are done. Creating the thing that needs testing and resources supporting that.
it('should be able to create its application test objects', function() {
var coffee = new App.Model.Coffee();
expect(coffee).toBeDefined();
expect(MOCK_GET_DATA).toBeDefined();
expect(MOCK_POST_DATA).toBeDefined();
});
In this case App.Model.Coffee
is the model declared in the application’s global
namespace. All of that code is brought into the test runner through various <script>
tags written in SpecRunner.html.
MOCK_GET_DATA
is an object defining what a coffee model definition looks like
when
calling to services. We can make these by looking at the “network” table in Chrome’s developer
tools window while the app runs against a live service. Capture that output and write a mock
data fixture.
var MOCK_GET_DATA = {
author: 'Ken Tabor',
ingredients: [18, 15, 1, 1, 1],
message: 'Heya everyone, we have a crazy big deadline coming up but I know we can do it. Let\'s enjoy a coffee and finish strong!',
name: 'Basic Drip',
readUrl: 'http://www.madefreshcoffee.com/read.php?sku=bd86292a-241a-11e2-b97c-12313d04a24a'
};
Atop this code block an application object is created for testing following expectations.
var coffee = new App.Model.Coffee(MOCK_GET_DATA);
Calling a function on the app object returns a value that’s compared to what’s expected.
it('should return the name', function() {
expect(coffee.getName()).toEqual('Basic Drip');
});
Creates its own application model for the suite of expectations following. Why bring in a new model? Just in case. Clean-room testing feels like the right thing to do most of the time. Realize that it() blocks can easily affect the test object creating side-effects rippling through down-stream expectations. Use your own judgement on this technique.
This block calls the model’s set function with an intended value, and then expects to find the proper values stored in the Backbone attribute. Why not use the app object’s corresponding .getIngredients() function? A fair question and we could do. Perhaps its just a matter of taste, but in this case I like the idea of testing more deeply by looking into the implementation details.
it('should set the new ingredients', function() {
coffee.setIngredients([24, 22, 22, 9, 3]);
expect(coffee.get('ingredients')).toEqual([24, 22, 22, 9, 3]);
});
Now we’re getting to a fun test. Jasmine “spies” are used switching away context from a JavaScript function for one of Jasmine’s own. Spies observe caller information and return with its own results (unit test) or passing through (integration test).
My example code shows one way of putting spies into action by short-circuiting
Backbone.Model.fetch()
calling to $.ajax()
by returning MOCK_GET_DATA
.
Jasmine
beforeEach()
and afterEach()
statements are used in another
attempt at being professional
and scientific creating clean well-known test objects before each it()
block.
Jasmine will call beforeEach
before each it()
block and afterEach
after each it()
block. No surprises there.
describe('when it fetches', function() {
var coffee;
beforeEach(function() {
spyOn($, 'ajax').andCallFake(function(options) {
options.success(MOCK_GET_DATA);
});
coffee = new App.Model.Coffee();
coffee.fetch();
});
afterEach(function() {
coffee = undefined;
});
});
Granted this seems a bit procedural, but it confirms an assumption that the proper data is available by checking each Backbone.Model attribute expected from a service response.
it('should be able to parse mocked service response', function() {
expect(_.isEmpty(coffee.attributes)).toEqual(false);
expect(coffee.get('author')).toEqual('Ken Tabor');
expect(coffee.get('ingredients')).toEqual([18, 15, 1, 1, 1]);
expect(coffee.get('message')).toEqual('Heya everyone, we have a crazy big deadline coming up but I know we can do it. Let\'s enjoy a coffee and finish strong!');
expect(coffee.get('name')).toEqual('Basic Drip');
expect(coffee.get('readUrl')).toEqual('http://www.madefreshcoffee.com/read.php?sku=bd86292a-241a-11e2-b97c-12313d04a24a');
});
Your model might have a more complicated .parse() function perhaps synthesizing new attributes having unpacked and interpreted the service response. Given that case you can see where this is an even more interesting test.
Of course there are more tests in this sample spec. Please read them when you pull down the
example code from my GitHub repo. The one checking $.ajax
parameters is unique.
Do I actually have favorite matchers? In fact I do. Jasmine matchers compare a test object result with an expected value. I keep my toolbox lightweight assuring that what I use I use often and well.
.toEqual(‘booga booga’);
.toBeDefined();
.toBeUndefined();
Consider using and mastering just a few matchers to start. Dip into the Jasmine docs from time to time drawing up a few more features calling you. Incorporate them into your project when you’re feeling confident.
Realize that other Jasmine matcher libraries exist and you ought to explore those to use straight-up or serve as a jumping-off point for building your own. I particularly admire jasmine-jquery because it exposes semantic tests for DOM elements. Awesome stuff for my upcoming article on unit testing Backbone.View objects.
My article is only the start of proper JavaScript unit testing. You’ll want to discover some of the following techniques for professional engineering:
.andCallThrough()
instead of .andCallFake()
Have a think on all of this. Unit testing using Jasmine is a wonderful way to power up the professional quality of your web app. Dynamic languages like JavaScript are nimble and flexible for us, but they don’t help craft fantastically stable application architecture.
Unit testing with Jasmine can build up your confidence level making programming life easier. Have a coffee and watch your application grow in a reliable manner.
You may enjoy my book if you learned from this article. It’s called Responsive Web Design Toolkit: Hammering Websites Into Shape. Get it on Amazon today!