This article is a crash course in writing maintainable JavaScript. We'll add features to a running example by iteratively following a simple principle: write a unit test, make it pass. Each test will serve as a quality feedback loop, creating both a safety net and an executable form of documentation for anyone who wants to change the production code. By starting each feature with a simple failing test we ensure that all features are tested. We avoid the cost of rewriting code to test it later. This is particularly valuable given the fact that JavaScript developers have so much rope to hang themselves with - consider how much global mutable state there is between the DOM API and the language itself.
Our running example is a casino slot machine with three reels. Each reel has five possible states, represented by images. Each reel randomly assumes a state when the play button of the slot machine is pressed. The slot machine balance will be incremented or decremented depending on whether or not all three reel states are equal.
Our tools will be stubs, mock objects and a little bit of dependency injection. We will use JsUnit to run unit tests and a JavaScript mock object library called JsMock. Integration testing, a compliment of unit testing, is beyond the scope of this article. This does not mean integration testing is less important - just that we will strive for quick feedback rather than the slower and more comprehensive feedback obtained with tools like Selenium and Watir.
JsUnit, a JavaScript Unit Testing Framework
JsUnit is an open source unit testing framework for JavaScript. It is inspired by JUnit and written entirely in JavaScript. In addition to being the most popular JavaScript unit testing framework it comes with a handful of ant tasks. The ant tasks enable developers to easily run test suites in Continuous Integration server builds. Continous Integration, another great practice that is beyond the scope of this article, is a 'force multiplier' for quality when combined with TDD.
Let’s start with the JsUnit test runner. The test runner is a plain HTML and JavaScript web page, meaning that your unit tests will be running directly in the browser or browsers you wish to support. Unzip the JsUnit download file and you will find testRunner.html in the root directory. You do not need to serve the test runner from a web server – simply load it by browsing to it via the file system.
The most important control of the test runner is the file input form field located at the top of the page. This control is meant to hold a path to a test page file or test page suite file. Now let’s look at a very simple example of a JsUnit test page.
<html> <title>A unit test for drw.SystemUnderTest class</title> <head> <script type='text/javascript' src='../jsunit/app/jsUnitCore.js'></script> <script type='text/javascript' src='../app/system_under_test.js'></script> <script type='text/javascript'> function setUp(){ // perform fixture set up } function tearDown() { // clean up } function testOneThing(){ // instantiating a SystemUnderTest, a class in the drw namespace var sut = new drw.SystemUnderTest(); var thing = sut.oneThing(); assertEquals(1, thing); } function testAnotherThing(){ var sut = new drw.SystemUnderTest(); var thing = sut.anotherThing(); assertNotEquals(1, thing); } </script> </head> <body/> </html>
JsUnit has many things in common with other xUnit frameworks. As you might expect the test runner loads the test page, and calls each test function. Each test function call is sandwiched between a call to setUp and tearDown. The setUp function provides the test author with his or her opportunity to optionally construct a test fixture. The test fixture refers to state intended to be the fixed for all tests in the page. The tearDown function provides the test author with his or her opportunity to clean up or reset the test fixture.
There is however a subtle difference in the test life cycle in JsUnit compared to other xUnit frameworks. Each test page is loaded into a separate window, preventing application code from overwriting the test framework code via open classes. Within each loaded window all unit test functions are invoked. The page is not reloaded for each test function. In JUnit on the other hand, the test page equivalent is a test case, and the test runner would instantiate a separate test case instance for each test method. In other words,
JsUnit loads a test page with N test functions one time,
JUnit creates a test case with N test methods N times
JavaScript developers are therefore on a more slippery slope because changes to test page state can influence the outcomes of subsequent tests. A Java developer however is not exposed to this risk when changing the state of a test case object. Why does JsUnit do this when the test page could simply be reloaded once per test? Because there are performance costs of recreating the DOM for every test function in a test suite. Fortunately JavaScript developers have to care less about side effects of global mutable state. On a programming platform such as the JVM or the CLR, a unit test that mutates a static variable can impact the outcome of all subsequent tests in the entire test suite, not just those tests belonging to the same test case.
The jsUnitCore.js script must be embedded in all test pages. This important file is located in the app directory of the unzipped JsUnit download file. It contains a handful of assertion functions that behave more or less identical to other xUnit frameworks. A subtle difference however stems from the fact that JavaScript has two notions of equality. JavaScript has an equals operator and a threequals operator. For example, the first expression below evaluates to true, the second evaluates to false:
0 == false
0 === false
How does this work? The equals operator is not as strict as the threequals operator, allowing the runtime to perform type conversion for the first boolean expression. Thus it is understandable for a novice to assume the following assertion will pass.
assertEquals(false, 0);
This assertion will in fact fail the test because the assertion functions provided by the JsUnit framework use the stricter threequals operator as opposed to the equals operator for all comparisons. By avoiding the equals operator JsUnit can avoid many false positive test runs.
Stubs vs. Mocks
Let’s look at stubs and mock objects with our running example, a slot machine. Because this unit test is focused on a single object, we will create a slot machine and refer to it as the system under test. Now let’s write a simple test to render the slot machine.
function testRender() { var buttonStub = {}; var balanceStub = {}; var reelsStub = [{},{},{}]; var randomNumbers = [2, 1, 3]; var randomStub = function(){return randomNumbers.shift();}; var slotMachine = new drw.SlotMachine(buttonStub, balanceStub, reelsStub, randomStub); slotMachine.render(); assertEquals('Pay to play', buttonStub.value); assertTrue(buttonStub.disabled); assertEquals(0, balanceStub.innerHTML); assertEquals('images/2.jpg', reelsStub[0].src); assertEquals('images/1.jpg', reelsStub[1].src); assertEquals('images/3.jpg', reelsStub[2].src); }
The testRender function stubs out two DOM elements, injects both into the constructor of the system under test and calls the render method. The test ends with assertions for the expected side effects of the render method. Notice that by stubbing out the DOM elements we can test the side effects of the render method without actually doing anything that could invalidate other tests within the test page. There is a trade off between this approach and using real DOM elements. Using real DOM elements is more likely to catch bugs related to cross browser incompatibilities but you are more likely to have bugs in your tests if the DOM state is not reset at end of each test or in tearDown.
The system under test does not make a direct global function call to Math.random in order to determine the initial image state of the reels. The slot machine instead relies on what is provided to it at creation time in order to obtain these numbers. This allows us to test a non deterministic piece of software as though it were entirely predictable. Notice how the test avoids the risks of mutable state and side effects by not overwriting the native Math.random implementation of the browser.
Hold on, wait a minute ... the test function has more than one assertion. Is this OK? There is a small group of people in the Agile community who think it's a sin to put more than one assertion per test. Test suites written for real applications making real money however are rarely written this way. Many of these people are astounded when they see how many assertions per test are found within the actual test suite for the JUnit framework itself.
The object's constructor and its render method look like this:
/** * Constructor for the slot machine. */ drw.SlotMachine = function(buttonElement, balanceElement, reels, random, networkClient) { this.buttonElement = buttonElement; this.balanceElement = balanceElement; this.reels = reels; this.random = random; this.networkClient = networkClient; this.balance = 0; }; drw.SlotMachine.prototype.render = function() { this.buttonElement.disabled = true; this.buttonElement.value = 'Pay to play'; this.balanceElement.innerHTML = 0; for(var i = 0; i < this.reels.length;){ this.reels[i++].src = 'images/' + this.random() + '.jpg'; } };
Let’s put some money into the slot machine. In this scenario, the slot machine will make an asynchronous call back to the server in order to retrieve the user’s balance. This is challenging because there is no network in a unit test and the AJAX call will fail. When we write unit tests we should strive for code that is free of side effects, and IO certainly falls under this category.
function testGetBalanceGoesToNetwork(){ var url, callback; var networkStub = { send : function() { url = arguments[0]; callback = arguments[1]; } }; var slotMachine = new drw.SlotMachine(null, null, null, null, networkStub); slotMachine.getBalance(); assertEquals('/getBalance.jsp', url); assertEquals('function', typeof callback); }
This test stubs out the network. What is a stub? and how is a stub different than a mock? Many developers often confuse these two terms as synonyms. The testing community reserves the word stub for state based testing. In JavaScript this usually just means a simple object literal with hard coded return values. The word mock on the other hand is reserved for interaction testing. Mocks can be behaviorally trained. These behaviors interact with the system under test and the interaction can be verified.
By stubbing out the network client we can now test the getBalance method. The object literal stub applied to the constructor records it’s interaction with the system under test via local variables url and callback. These local variables give us something to perform assertions on at the end of the test. Unfortunately we have used the wrong tool for the job. This is a classic example of the limitations of stubs and why mock objects serve a purpose. The purpose of this test is not to verify the behavior of the system under test given a certain injected state; this test is concerned with verifying the interaction of a drw.SlotMachine instance with one of its collaborators, the network client.
JsMock, a Mock Object Library for JavaScript
If you look closely you can see that testGetBalanceGoesToNetwork creates it's own miniature mocking framework. Now let’s refactor the test to use a general purpose mocking framework. We do this by adding a single script tag to the test page and rewriting the test like this:
<script type='text/javascript' src='../jsmock/jsmock.js'></script> function testGetBalanceWithMocks(){ var mockControl = new MockControl(); var networkMock = mockControl.createMock({ send : function() {} }); networkMock.expects().send('/getBalance.jsp', TypeOf.isA(Function)); var slotMachine = new drw.SlotMachine(null, null, null, null, networkMock); slotMachine.getBalance(); mockControl.verify(); }
The test now gives us the same feedback in fewer lines of code and we have laid the groundwork for much cleaner foundation to build on for future tests. How does this work? The first line of code creates an object using the MockControl constructor provided by JsMock. The test then creates a mock object with a send method. In an application with an actual NetworkClient class we would not even have to apply a object literal to the createMock method. JsMock is able to infer this via the prototype:
var mock = mockControl.createMock(NetworkClient.prototype);
After a mock object for the network client has been created it is programmed to expect the send method to be called once with certain parameters. We care that the server resource name is correct and that the second argument is a callback function. The mock object is injected into the constructor of the system under test and the test concludes by verifying this interaction via the verify method of the MockControl object. If for any reason the slot machine implementation didn't call the send method on the network client, or if it were to fail to meet the parameter expectations, the verify method would throw an exception and the test would fail.
Now let’s make another test to verify when and how often a drw.SlotMachine instance goes to the network. If the getBalance method is called before the server response is complete, we don’t want the balance to be retrieved twice. This could result in the slot machine balance reflecting the user’s balance twice over, and some extra bandwidth.
function testGetBalanceWithMocksToTheNetworkOnce(){ var mockControl = new MockControl(); var networkMock = mockControl.createMock({ send : function() {} }); networkMock.expects().send('/getBalance.jsp', TypeOf.isA(Function)); var slotMachine = new drw.SlotMachine(null, null, null, null, networkMock); slotMachine.getBalance(); slotMachine.getBalance(); // no response from server yet slotMachine.getBalance(); // still no response mockControl.verify(); }
Remember our first crack at this? When we created our own miniature mocking framework? That might have seemed like a practical solution then but imagine how much code it would take to test an interaction like this. Just for arguments sake, let’s look at a flawed pure stub solution.
function testGetBalanceFlawed(){ var networkStub = { send : function() { if(this.called) throw new Error('This should not be called > 1 time'); this.called = true; } }; var slotMachine = new drw.SlotMachine(null, null, null, null, networkStub); slotMachine.getBalance(); slotMachine.getBalance(); // no response from server yet slotMachine.getBalance(); // still no response }
This test asserts that the network client is only used once by simply throwing an error from the network stub after the first use of it. There is a subtle problem here because the test is handing control of the assertion over to the object it’s supposed to be testing. For example, if system under test were to call the send function of the network stub multiple times, but swallow the exceptions thrown by it, the test would never fail because the test runner would simply never receive notification of a problem. There are ways around this by creating a more elaborate miniature mocking framework but it will always be easier to simply use a general purpose one like JsMock.
JsMock does not just give us the ability to test method call order and parameter values. Here’s a test to demonstrate how the slot machine behaves in the event of a network failure.
function testGetBalanceWithFailure(){ var buttonStub = {}; var mockControl = new MockControl(); var networkMock = mockControl.createMock({ send : function() {} }); networkMock.expects() .send('/getBalance.jsp', TypeOf.isA(Function)) .andThrow('network failure'); var slotMachine = new drw.SlotMachine(buttonStub, null, null, null, networkMock); slotMachine.getBalance(); assertEquals('Sorry, can't talk to the server right now', buttonStub.value); mockControl.verify(); }
Here we are verifying that the slot machine can fail gracefully in the event of a network failure. This is a good example of when unit testing can really outperform system integration testing. Can you imagine how much money and time it would cost to manually simulate a network failure for every integration point with the server during every a QA/Release cycle?
The getBalance method implementation now looks like this:
drw.SlotMachine.prototype.getBalance = function() { if(this.balanceRequested) return; try{ // this line of code requires the very excellent functional.js // library, found at http://osteele.com/sources/javascript/functional this.networkClient.send('/getBalance.jsp', this.deposit.bind(this)); this.balanceRequested = true; }catch(e){ this.buttonElement.value = 'Sorry, can't talk to the server right now'; } };
One drawback of mocks is that they are considerably more coupled to the system under test than a stub, at least out of the box. You want a test to fail when the system under test no longer produces an expected behavior – you don't want a test to fail on every change to encapsulated implementation details. To remedy this situation JsMock provides the ability to relax expectations. You’ve seen an example of this already. When we trained our network mock object, we wrote this:
networkMock.expects().send('/getBalance.jsp', TypeOf.isA(Function));
We didn’t specify which function callback would be applied as the second argument, just that a function callback would be applied. If we wanted to loosen these expectations even further, we might try something like this:
networkMock.expects().send(TypeOf.isA(String), TypeOf.isA(Function));
If we wanted a reference to the actual callback being passed to the send method of the network client mock, we could make use of the andStub method of the JsMock framework:
var depositCallback; networkMock.expects() .send('/getBalance.jsp', TypeOf.isA(Function)) .andStub( function(){depositCallback = arguments[1];} ); depositCallback({responseText:"10"});
Two gotchas on mock objects before we proceed. Notice how each test ends with a call to the verify method of the MockControl. This is important. A unit test that does not call verify is a unit test that cannot fail. It occurs to many developers after authoring a few standard unit test functions that it would be better to move the call to verify from the test functions to the tearDown function. While this will save you a few lines of code and it relieves you of ever having to remember this very important detail at the end of each test function, it unfortunately will present you with a new problem. An exception thrown in tearDown can be masked by the first exception thrown by the test. A second pitfall is that some developers who are new to mock objects will often overuse them. More specifically, they try to use them as a all out replacement for stubs. Don’t do this. Use stubs for state based testing, use mocks for interaction based testing.
A Winning Scenario Test
We can use everything we’ve learned to test the following scenario. This test simulates a user losing then winning with the slot machine.
function testLoseThenWin(){ var buttonStub = {}; var balanceStub = {}; var reelsStub = [{},{},{}]; // a losing combination, followed by a winning combination var randomNumbers = [2, 1, 3].concat([4, 4, 4]); var randomStub = function(){return randomNumbers.shift();}; var slotMachine = new drw.SlotMachine(buttonStub, balanceStub, reelsStub, randomStub); var balance = 10; slotMachine.deposit({responseText: String(balance)}); slotMachine.play(); assertEquals(balance - 1, balanceStub.innerHTML); assertEquals('Sorry, try again', buttonStub.value); slotMachine.play(); assertEquals('balance - 2 + 40', 48, balanceStub.innerHTML); assertEquals('You Won!', buttonStub.value); assertEquals('images/4.jpg', reelsStub[0].src); assertEquals('images/4.jpg', reelsStub[1].src); assertEquals('images/4.jpg', reelsStub[2].src); }
The play method implementation of the drw.SlotMachine class looks like this:
drw.SlotMachine.prototype.play = function(){ var outcomes = []; var msg = 'Sorry, try again'; for(var i = 0; i < this.reels.length; i++){ this.reels[i].src = 'images/' + (outcomes[i] = this.random()) + '.jpg'; } if(outcomes[0] == outcomes[1] && outcomes[0] == outcomes[2]){ msg = 'You Won!'; this.balance += (outcomes[0] * 10); } this.buttonElement.value = msg; this.balanceElement.innerHTML = --this.balance; }; |
And finally a working demonstration of a slot machine:
<html> <title>A Slot Machine Demonstration</title> <head> <script type='text/javascript' src='functional.js'></script> <script type='text/javascript' src='slot_machine.js'></script> <script type='text/javascript' src='network_client.js'></script> <script type='text/javascript'> window.onload = function(){ var leftReel = document.getElementById('leftReel'); var middleReel = document.getElementById('middleReel'); var rightReel = document.getElementById('rightReel'); var random = function(){ return Math.floor(Math.random()*5) + 1; // generate 1 through 5 }; slotMachine = new drw.SlotMachine(document.getElementById('buttonElement'), document.getElementById('balanceElement'), [leftReel, middleReel, rightReel], random, new NetworkClient()); slotMachine.render(); slotMachine.getBalance(); }; </script> </head> <body id='body'> <div style="text-align:center; background-color:#BFE4FF; padding: 5px; width: 160px;"> <div>Slot Machine Widget</div> <div style="padding: 5 0 5 0;"> <img id='leftReel'/> <img id='middleReel'/> <img id='rightReel'/> </div> <div>Balance: <span id="balanceElement"></span></div> <input id="buttonElement" style="width:150px" type="button" onclick="slotMachine.play()"></input> </div> </body> </html>
Reference Materials
- JSMock is a fully featured Mock Object library for JavaScript authored by Justin DeWind
- JsUnit is a Unit Testing framework for client-side (in-browser) JavaScript
- Mocks Aren’t Stubs, an article by Martin Fowler
- Functional is a library for functional programming in JavaScript authored by Oliver Steele
- Dependency Injection, an article by Martin Fowler
About the Author
Dennis Byrne lives in Chicago and works for DRW Trading, a proprietary trading firm and liquidity provider. He's a writer, presenter and active member of the open source community.