It is an exciting time to be a JavaScript developer; technologies have matured, web browsers are more standardized and there are new things to play with every day. JavaScript has become an established language, and the Web is the true open platform of today. We've seen the rise of single-page web applications, the proliferation of MVC frameworks such as Backbone.js and AngularJS, the use of JavaScript on the server with Node.js, and even mobile applications created entirely with HTML, JavaScript, and CSS using technologies such as PhoneGap.
From its humble beginning with handling HTML forms, to the massive applications of today, the JavaScript language has come very far, and with it, a number of tools have matured to ensure that you can have the same level of quality with it that you have with any other language.
This book is about the tools that keep you in control of your JavaScript development.
There are many complications when dealing with client JavaScript code; the obvious one is that you cannot control the client's runtime. While on the server you can run a specific version of your Node.js Server, you can't oblige your clients to run the latest version of Chrome or Firefox.
The JavaScript language is defined by the ECMAScript specification; therefore each browser can have its own implementation of a runtime, meaning there could be small differences or bugs between them.
Besides that, you have issues with the language itself. JavaScript was developed by Brendan Eich in just 10 days, under a lot of management pressure on Netscape. Although it got itself right in its simplicity, first-class functions, and object prototypes, it also introduced some problems with the attempt to make the language malleable and allow it to evolve.
Every JavaScript Object is mutable; this means that there is nothing you can do to prevent a module from overwriting pieces of other modules. The following code illustrates how simple it is to overwrite the global console.log
function.
console.log('test'); >> 'test' console.log = 'break'; console.log('test'); >> TypeError: Property 'log' of object #<Console> is not afunction
This was a conscious decision on the language design; it allows developers to tinker and add missing functionality to the language. But given such power, it is relatively easy to make a mistake.
A newer version of the language introduced the
Object.seal
function, which prevents further changes on any object once called. But its current support is not widespread; it was only introduced on Internet Explorer 9 and is currently missed on Opera.
Another problem is in how JavaScript deals with type. In other languages an expression like '1' + 1
would probably raise an error, in JavaScript, due to some non-intuitive type coercion rules, the above code results in '11'
. But the main problem is in its inconsistency; on a multiplication the string is converted to a number, so '3' * 4
, is actually 12
.
This can lead to some hard-to-find problems on big expressions. Suppose you have some data coming from a server, and although you are expecting numbers, one value came as a string:
var a = 1, b = '2', c = 3, d = 4; var result = a + b + c * d;
The above result value is '1212'
, a string.
These are just two common problems faced by developers. Throughout the book, you are going to be applying best practices and writing tests to guarantee that you don't fall on these and other pitfalls.
Jasmine is a little Behavior-Driven Development framework, created by the guys at Pivotal Labs to allow you to write automated JavaScript unit tests.
But before we can go any further, first we need to get some fundamentals, starting with what is a test unit.
A test unit is a piece of code that tests a functionality unit of the application code. But sometimes it can be tricky to understand what a functionality unit can be, so for that reason, Dan North came up with a solution in the form of Behavior-Driven Development (BDD), which is a rethink of Test-Driven Development (TDD).
In traditional unit testing practice, the developer is left with no guidelines on how to start in the process of testing, what to test, how big a test should, be or even how to call a test.
To fix these problems, Dan took the concept of user stories from the standard agile construct, as a model on how to write tests.
For example, a music player application could have an acceptance criterion like:
Given a player, when the song has been paused then it should indicate that the song is currently paused.
As you can see, this acceptance criterion is written following an underlying pattern:
given [an initial context]
when [an event occurs]
then [ensure some outcome]
In Jasmine this translates into a very expressive language that allows tests to be written in a way that reflects actual business values. The above acceptance criterion written as a Jasmine test unit would be:
describe("Player", function() { describe("when song has been paused", function() { it("should indicate that the song is paused", function() { }); }); });
You can see how the criterion translates well into the Jasmine syntax. In the next chapter we will get into the details of how these functions work.
With Jasmine, as with other BDD frameworks, each acceptance criterion directly translates to a test unit. For that reason, each test unit is usually called a spec, short for specification. During the course of this book, we will be using this terminology.
To get started with Jasmine is actually pretty simple. Open the Jasmine website at http://pivotal.github.io/jasmine/ and download the Standalone Release.
While at the Jasmine website, you might notice that it is actually a live page, executing the specs contained on it. This is made possible by the simplicity of the Jasmine framework, allowing it to be executed in the most diverse environments.
After you've downloaded the distribution and uncompressed it, you can open the SpecRunner.html
file on your browser. It will show the results of a sample test suite (including the acceptance criterion we showed you earlier):
This SpecRunner.html
file is a Jasmine browser spec runner. It is a simple HTML file that references the Jasmine code, the source files, and the test files. For convention purposes we are going to refer to this file simply as
runner.
You can see how simple it is by opening it on a text editor. It is a small HTML file that references the Jasmine source:
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script>
References the source files:
<!-- include source files here... --> <script type="text/javascript" src="src/Player.js"></script> <script type="text/javascript" src="src/Song.js"></script>
References the spec files:
<script type="text/javascript" src="spec/PlayerSpec.js"></script>
And contains a small Jasmine boot code:
var jasmineEnv = jasmine.getEnv(); jasmineEnv.updateInterval = 1000; var htmlReporter = new jasmine.HtmlReporter(); jasmineEnv.addReporter(htmlReporter); jasmineEnv.specFilter = function(spec) { return htmlReporter.specFilter(spec); }; var currentWindowOnload = window.onload; window.onload = function() { if (currentWindowOnload) { currentWindowOnload(); } execJasmine(); }; function execJasmine() { jasmineEnv.execute(); }
In Chapter 8, Build Automation, we are going to get a deep understanding on how to customize this runner, but for now you can see that:
It configures a
jasmine.HtmlReporter
, to report the test results on HTMLIt adds a callback on
window.onload
And it executes the Jasmine test suite through
jasmineEnv.execute()
once the window has been loaded
It is important that you take a glimpse at this code to understand the flexibility that Jasmine provides; you can see that it has no dependencies, and even though the results are being rendered on an HTML file, this can be easily changed by switching the reporter:
jasmineEnv.addReporter(new jasmine.TerminalReporter());
And then running the same code on a headless browser such as PhantomJS, and having the results written on the console.
A headless browser is a browser environment without its graphical user interface. It can either be an actual browser environment, such as PhantomJS, which uses the WebKit rendering engine, or a simulated browser environment, such as Envjs.
But Jasmine can also be used to test server code, using the same reporter on a Node.js runtime.
This Jasmine flexibility is amazing, because you can use the same tool to test all sorts of JavaScript code.
In this chapter, you have seen some of the motivations behind testing a JavaScript application. I showed you some common pitfalls of the JavaScript language and how BDD and Jasmine both help you write better tests.
You have also seen how easy it is to download and get started with Jasmine. In the next chapter, you are going to learn how to think in BDD and code your very first spec.