Skip to main content

Unit Testing with the Jasmine JS


Recently had the opportunity to do pair programming on a project at work. Paring has proven to be invaluable in passing knowledge over to other developers on the team. It is in this paring that I was exposed to Jasmine test behavior development.
Jasmine was developed at Pivotal Labs back in 2001, and was the first unit testing framework for JavaScript. I won't go into why you should be doing test or behavior driven development, but if your company has adopted the Agile methodology, Jasmine fits nicely into the flow of the Agile process. It gives you the additional security knowing that if at some point in the future someone updates your JavaScript, unless the test suite is updated as well things will begin to break.

The Suite or Spec

Conceptually, a suite or spec is a bunch of related tests clubbed together with an expected result or outcome. Suites or specs are defined with the global "describe" function. This describes what is being tested. The describe function takes 2 parameters, a text string and a call back function. The cool thing is the description attribute of the function can be derived from the description of an agile story. "....we need to display to the user a red check mark in addition to highlighting the field in red that has an incorrect or improper value submitted." From this we can begin writing our suite or spec.
I sometimes like to nest the describe function to coincide with the user's actions.
define(['base/search_errors', 'lodash'], function (apsSearchErrors, _) {
describe("On visiting the search page", function () {
describe("When errors return on submit", function () {
});
});
});
view raw adding Describe hosted with ❤ by GitHub
Inside the describe method we add our "it" method, which again takes 2 parameters, a text string for the description and a call back function.
define(['base/search_errors', 'lodash'], function (searchErrors, _) {
describe("On visiting the search page", function () {
describe("When errors return on submit", function () {
it("Should add the fieldErrorBorder class to the fields that
return with the error class", function () {
});
});
});
});
});
As you can see we have added the agile story; "On visiting the search page When errors return on submit Should add the fieldErrorBorder class to the fields that return with the error class" to the code. It also acts as your code comment.
define(['base/cas/app/aps_search_errors', 'lodash'], function (apsSearchErrors, _) {
describe("On visiting the APS search page", function () {
describe("On submit returns errors", function () {
it("Should add the fieldErrorBorder class to the fields that return with the error class", function () {
setFixtures("<input type=hidden id='searchCriteriaLastName' class='error fieldErrorBorder'/>\n\
<input type=hidden id='searchCriteriaSsn' class='error fieldErrorBorder'/>\n\
<input type=hidden id='searchCriteriaAuthText' class='error fieldErrorBorder'/>");
_(["#searchCriteriaAuthText", "#searchCriteriaSsn", "#searchCriteriaLastName"])
.each(function (id) {
searchErrors.setBorderForErrors($(id));
expect($(id)).toHaveClass("fieldErrorBorder");
});
});
});
At this point I set up our fixtures and call backs to the JavaScript source file I'm writing the test against. I have created the fieldErrorBorder class that contains the input field error and the red check mark and placed it in the CSS file so it can be added using the JavaScript source file when the error condition occurs.

The source file

define(['jquery', 'lodash'], function ($, _)) {
var errors = $('.error');
_(["#searchCriteriaAuthText", "#searchCriteriaSsn", "#searchCriteriaLastName"]).each(function (id) {
setBorderForErrors($(id));
$(id).blur(function () {
setErrorHandlers($(id));
});
});
function setBorderForErrors(element) {
if (element.is(errors)) {
element.addClass("fieldErrorBorder");
}
}
function setErrorHandlers(element) {
if (element.is(errors)) {
element.removeClass("fieldErrorBorder");
element.prev().removeClass("error");
element.next('span').css('display', 'none');
}
}
return {
setBorderForErrors: setBorderForErrors,
setErrorHandlers: setErrorHandlers
};
});
I'm also importing the "lodash" JavaScript library. Lodash is really excellent at array handling and array I/O.  
In my source file I'm using the "Revealing Module Pattern" to expose the methods to the spec file. Simply add your existing functions to the object literal, the "Return", and reference it as such: searchErrors.setBorderForErrors().

Acceptance criterion

Acceptance tests are directly related to the software requirements specs. Trace-ability between requirements and implementation as well as between requirements and acceptance tests is key to test driven development.

Going forward keep in mind:
  • Test written first
  • Given [something is going to happen]
  • When [something happens]
  • Then [we expect this to happen]
  • Tests are written to reflect business value.
  • Think in plain old English description of what you are doing or intending to do.
  • Tests are written first tested and failed, code written after.
Test driven development and Behavior driven development is now a de-facto standard for developers. I have just touched the surface of the power of Jasmine, Jasmine jQuery, and Lodash. If written correctly, any changes, accidental or otherwise, to your JavaScript will happily throw an error thus saving the developer countless headaches and hours of lost productivity. Estimate correctly during grooming. ½ day to write the JavaScript may now be 1 day to write the JavaScript and the spec. Write the test first. Make it fail. Consider how you will expose your methods to the web and to the spec. Keep it simple. Code the test, run the test, code, run the test, rinse and repeat…etc. Fix errors pre commit. Clear runner cache often, run from build or command line. Happy development!

Reference

Comments

Popular posts from this blog

The ICMP protocol

Let's look into the ICMP protocol. Specifically, ping and traceroute. ICMP is the Internet Control Message Protocol and is a component of the IP Layer. Basically, used by hosts to communicate diagnostic network layer information that is carried in the IP payload. It communicates error messages which are acted on by the IP layer or the UDP or TCP protocols. All of the exercises were carried out using the open source network protocol analyzer "Wireshark". www.wireshark.org Describe in detail the protocols ARP and ICMP. ARP is the Address Resolution Protocol is similar to that of DNS. Where DNS resolves IP addresses to domain names, ARP resolves network layer IP addresses to link layer MAC addresses. In order to send a datagram the source must give the adaptor the IP address and the MAC address. For example, host A wants to send a packet to host B. Host A uses a cached ARP table to look up the IP address for any existing records of host B's MAC address. If the MA...

Router modes and channel impairments

What does it mean for a wireless network to be operating in "infrastructure mode?" Clients connected to a base station (an access point or router) are operating in infrastructure mode. They communicate indirectly through this access point or router which serves as a bridge to a wired network. Ad-hoc mode networks are networks that do not rely on a router or access point infrastructure. As a result, each client participates in the routing by forwarding data to the other connected clients. What are the differences between the following types of wireless channel impairments:  path loss, multipath propagation, interference from other sources? Multipath propagation is when packet loss occurs due to electromagnetic waves reflecting off of ground objects which then take paths of different lengths between sender and receiver. Interference from other sources happens when there is interference from radio sources transmitting in the same frequency band. Wireless phones and wirel...

:nth-child structural pseudo-class selectors

There are 4 pseudo-class expressions that are part of the :nth-child pseudo-class. Structural pseudo-class selectors target HTML elements based on the DOM tree. Basically, elements that cannot easily be targeted by simple selectors or combinations of selectors. What makes pseudo-classes so handy is the ability style elements dynamically based on its position in the DOM. :nth-of-type(N) :nth-last-child(N) :nth-child(N) :nth-last-of-type(N) :nth-of-type(N) selector My favorite of the 4 is the :nth-of-type(N) selector. The nth-of-type selector allows you to select child elements of a parent based on the particular type of the element, for example every 5th "li" element in a list. You can select even or odd elements, or the nth (order number) child in the group of elements. The class accepts the argument "n" which can can be a keyword, a number, or strings "odd", "even", or an expression "-n+3". Let's look at a simple but ef...