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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(['base/search_errors', 'lodash'], function (apsSearchErrors, _) { | |
describe("On visiting the search page", function () { | |
describe("When errors return on submit", function () { | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 () { | |
}); | |
}); | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
}); | |
}); | |
}); |
The source file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
}; | |
}); |
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:
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!
Comments