UI Code Expert
JavaScript Unit Testing with Visual Studio
Mark Michaelis walks you through the Visual Studio tooling and project setup you'll need to get the most out of your JavaScript unit testing.
As I detailed in my recent article "A TypeScript Primer," I, like many developers, have a love/hate relationship with JavaScript. I love JavaScript because of its ubiquity; from iPhone to Android to Windows Phone, JavaScript just works. From iOS to UNIX to Windows, JavaScript continues to just work. (Admittedly, there are some idiosyncrasies, but for the most part -- at least from a language perspective -- it just works.)
Unfortunately, JavaScript is lacking in its ability to verify intent. It does what you tell it to do -- not necessarily what you want it to do. (If you figure out the secret for getting a computer to consistently do what you want rather than what you tell it, please let me know! I would like to partner with you on a business idea or two.) Table 1has a summary of JavaScript's good and bad:
Good |
Bad |
Ubiquity |
No inherent OO |
Closures |
Script (not compiled) |
Dynamic at runtime |
Lacking type safety |
Ramp-up is trivial |
Dynamic at runtime |
Vendor independence |
Modularization/Organization/Namespaces |
Frameworks are vibrant |
Limited IntelliSense capabilities |
Table 1. JavaScript, the Good and the Bad
|
Of all these characteristics, it's JavaScript's lack of type safety coupled with not having a compiler fail in the capacity to verify intent. That is, of course, if you don't have unit tests. Unit tests can compensate for the lack of type safety. And unlike with .NET, where unit tests focus mainly on functional verification (since the compiler eliminated the majority of typos), unit tests in JavaScript do both. In summary, JavaScript unit testing is all the more critical because it's responsible to not only verify functionality, but also to verify syntax and language semantics.
In this article, I'm going to focus on the Visual Studio tooling and project setup requirements needed to get the most out of your JavaScript unit testing.
Tooling
The two most popular Visual Studio integrated tools for JavaScript unit testing are ReSharper and Chutzpah (a Yiddish word about having the audacity to say things as they are -- good or bad). Chutzpah is an open source Visual Studio extension and JavaScript test runner written by Matthew Manela. ReSharper is the well-known JetBrains tool, famous for its C# and JavaScript refactoring capabilities.
Both tools are Visual Studio extensions, so installing either into Visual Studio is nothing more than clicking the Tools->Extensions and Updates... menu and searching for "JavaScript Unit Testing" or simply "Chutzpah" or "ReSharper."
At the core of most JavaScript unit testing lies a headless browser, PHATOMJS.EXE. When this browser launches, it hosts HTML that in turn references your JavaScript files. In addition to the JavaScript files that you supply from your Web project (and the frameworks like JQuery that you reference as part of your production code), JavaScript unit testing generally relies on a unit testing framework. The two primary unit testing frameworks, both of which are open source, are QUnit and Jasmine. Both are well suited for the task of unit testing but each brings a slightly different style to the unit test. QUnit follows the traditional unit-testing style test format, while Jasmine is a behavioral-driven design (BDD) testing framework.
QUnit
As stated on the QUnit Web site, "QUnit is a powerful, easy-to-use JavaScript unit testing framework. It's used by the jQuery, jQuery UI and jQuery Mobile projects and is capable of testing any generic JavaScript code, including itself!" Code Listing 1provides a sample QUnit test file.
Code Listing 1: A Sample QUnit Series of Tests
/// <reference path="QUnit.js" />
/// <reference path="../../intellitect.sharepoint.web/scripts/SampleRestService.js" />
var restService = null;
module("SampleRestService.getToken()", {
setup: function () {
restService = new SampleRestService("http://IntelliTect.com/Blog");
},
teardown: function () {
restService = null;
}
});
test("Provide valid credentials", function () {
var token = restService.getToken("Inigo.Montoya", "Ykmfptd!");
equal(token, "ecy8b081wh6owf8o",
"The token value returned was not as expected.");
});
test("Prevent empty string for the name", function () {
raises(function() {
var token = restService.getToken("", "Ykmfptd!");
}, "Unexpectedly, no error was raised given a blank name.");
});
test("Prevent empty null for the password", function () {
raises(function () {
var token = restService.getToken("Inigo.Montoya", null);
}, "Unexpectedly, no error was raised given a null password.");
});
module("SampleRestService.downloadFile()")
test("Throw an exception if file does not exist.", function () {
raises(function () {
var restService =
new SampleRestService("http://IntelliTect.com/Blog",
"Inigo.Montoya", null);
var file = restService.downloadFile("Bog.us");
}, "Unexpectedly, no error was raised given an invalid file.");
});
As one would expect, QUnit supports the standard unit testing constructs, including grouping tests into constructs (using module), pre- and post-test execution steps (setup/teardown members), and a variety of assertions: ok, equal, notEqual, strictEqual, notStrictEqual, deepEqual, notDeepEqual, raises. Essentially, the structure mimics that of a developer unit-testing library.
Jasmine
Although very similar, Jasmine's BDD-based API involves defining a Spec -- a grouping of tests or conditions to verify. The spec places each test into a context or scenario and comprises a suit of tests (see Code Listing 2).
Code Listing 2: Jasmine Unit Testing Sample
/// <reference path="Jasmine.js" />
/// <reference path="../../intellitect.sharepoint.web/scripts/SampleRestService.js" />
var restService = null;
describe("SampleRestService.getToken()", function () {
var restService = null;
beforeEach(function () {
restService = new SampleRestService("http://IntelliTect.com/Blog");
});
afterEach(function () {
restService = null;
});
it("Provide valid credentials", function () {
var token = restService.getToken("Inigo.Montoya", "Ykmfptd!");
expect(token).toBe("ecy8b081wh6owf8o");
});
it("Prevent empty string for the name", function () {
var emptyUserNameExpression = function () {
var token = restService.getToken("", "Ykmfptd!");
};
expect(emptyUserNameExpression).toThrow();
});
it("Prevent empty null for the password", function () {
var nullPasswordExpression = function () {
var token = restService.getToken("Inigo.Montoya", null);
};
expect(nullPasswordExpression).toThrow();
});
});
describe("SampleRestService.downloadFile()", function() {
it("Throw an exception if file does not exist.", function () {
var fileDoesNotExistExpression = function () {
var restService =
new SampleRestService(
"http://IntelliTect.com/Blog", "Inigo.Montoya", null);
var file = restService.downloadFile("Bog.us");
};
expect(fileDoesNotExistExpression).toThrow();
});
});
In Jasmine, each test is identified by the it() function; and, like QUnit, tests can be grouped together by context using the describe function in Jasmine. In fact, the describe() functions can be nested to create a hierarchical grouping of tests. The assertions in Jasmine are all functions available from the expect() function return, and include verifications like toBe(), toEqual(), toMatch(), toBeUndefined(), toContain(), toThrow() and so on. Prefixing any of these calls with a not() call asserts the opposite. Jasmine provides specialized mock, fake and spies (verification that functions are called) functionality.
Under the Covers
As mentioned earlier, under the covers, JavaScript unit testing generally relies on a headless browser called PhantomJS.exe, along with a series of HTML files that reference your JavaScript. However, rather than manually creating these HTML files (although you certainly can), Chutzpah generates them for you based on the JS files within your test project. Furthermore, Chutzpah then determines all the tests within your project (whether QUnit or JUnit) and loads them into the Visual Studio Test Explorer Windows (see Figure 1).
Chutzpah also supports a command-line execution via the chutzpah.console.exe executable. (Unfortunately, you might find that using chutzpah.console.exe is required if using Visual Studio 2013 in combination with Windows 8.X and some NVidia drivers.) Additionally, this command-line test runner can be leveraged on your build server even when the Chutzpah extension is not installed (using the TFS Build Service, for example).
Significantly Improve Code Quality
As mentioned earlier, JavaScript unit testing is even more critical than .NET unit testing because of the fact that there is no compiler. However, it isn't that hard to get started. Furthermore, although it may seem a little time-consuming when you start, those disciplined enough to stick with it will quickly discover that, in fact, unit testing their JavaScript even for just a short while is faster than re-launching the browser and navigating to a particular page (perhaps reclogging in, for example) -- significantly faster in the long run. Furthermore, it has the advantages that the tests can be checked in and executed as part of the build process. Therefore, delay no further. Get started with your JavaScript unit testing today and experience one of the most significant ways to improve your code quality in a single practice.
About the Author
Mark Michaelis (http://IntelliTect.com/Mark) is the founder of IntelliTect and serves as the Chief Technical Architect and Trainer. Since 1996, he has been a Microsoft MVP for C#, Visual Studio Team System, and the Windows SDK and in 2007 he was recognized as a Microsoft Regional Director. He also serves on several Microsoft software design review teams, including C#, the Connected Systems Division, and VSTS. Mark speaks at developer conferences and has written numerous articles and books - Essential C# 5.0 is his most recent. Mark holds a Bachelor of Arts in Philosophy from the University of Illinois and a Masters in Computer Science from the Illinois Institute of Technology. When not bonding with his computer, Mark is busy with his family or training for another triathlon (having completed the Ironman in 2008). Mark lives in Spokane, Washington, with his wife Elisabeth and three children, Benjamin, Hanna and Abigail.