This post explores how to use Jasmine to test my small custom DOM utility library. Jasmine is a Behaviour Driven Development (BDD) testing framework for JavaScript.
Download code/resources for this post from GitHub
You can read about how I created my custom DOM utility in these 3 posts:
Build your own DOM utility part 1
Build your own DOM utility part 2
Build your own DOM utility part 3
Before we start, if you are new to Jasmine you should take a look at their documentation
here. The 2 main Jasmine functions we
will use in this post are describe
and it
. The former, describe
creates
a test suite, useful for grouping tests together into logical and organised blocks, and it
creates a spec, or a test. Ideally, we want all tests within all suites to pass. This means our code is
functioning as we expect it to.
$DOM()
function
Because we are testing a set of DOM utility functions, I'll start by defining a function
to wrap document.querySelectorAll
, which returns the results as a standard JavaScript
array instead of a NodeList
(this is done using the Array slice method). This function
which I've named qsa
will come in handy for testing the element selection functionality
of my custom DOM utility.
var qsa = function qsa(selector, root) {
root || (root = document);
return Array.prototype.slice.call(root.querySelectorAll(selector));
};
We'll begin by creating a suite which will contain tests for testing the main $DOM
function, which can create new elements on the fly or select existing elements. Our first test
will test if our utility creates new elements on the fly as expected:
describe('$DOM function', function() {
// Test the $DOM function for creating new element
it('should create new element', function() {
var $el = $DOM('≤div>'),
el = $el.get();
expect(el instanceof Element).toEqual(true);
expect(el.tagName).toEqual('DIV');
});
});
Notice there are two expect
functions in the test. The first says that we expect
the the result of our $DOM
call to actually be an object of class Element
.
The second expect
says that we expect the new element to be a div element. When we pass
a statement to Jasmine's expect
function, we get back an object which we can chain other
Jasmine matcher functions onto, such as toEqual
, toBeLessThan
, toContain
,
toBeDefined
, toBeTruthy
, and so on. We can even test for the inverse cases using
assertions like expect(x).not.toEqual(0)
. For a test to pass, all assertions within it must
evaluate to true.
Let's add a second test to the suite. We will test whether element selection functionality is working
correctly or not. We'll test 3 different selectors to see if we get correct results. We compare our
results with those returned from our qsa
function.
it('should select elements matching specified selector', function() {
var $div, div;
// Select all divs
$div = $DOM('div'),
div = $div.map(function(d) { return d.get(); });
expect(div).toEqual(qsa('div'));
// Select all elements with the `test` class
$div = $DOM('.test'),
div = $div.map(function(d) { return d.get(); });
expect(div).toEqual(qsa('.test'));
// Select element with id `third-test`
$div = $DOM('#third-test')[0];
expect($div.get()).toBe(qsa('#third-test')[0]);
});
Next we need to test all the methods defined in the $DOM
utility. I use a suite
for each method. As an example, let's test the hasClass()
method:
describe('hasClass() method', function() {
var $el;
beforeEach(function() {
$el = $DOM('.test')[0];
});
// Test on class that is known to be present
it('should confirm presence of class', function() {
expect($el.hasClass('test')).toEqual(true);
});
// Test on class that is known not to be present
it('should confirm absence of class', function() {
expect($el.hasClass('abc')).toEqual(false);
});
});
As you can see, testing the hasClass()
method is relatively straightforward.
Let's test the data()
method. This is one of the more complicated methods, in
the sense that it can be called in a variety of ways to get different results. It can be used
in the following ways:
Therefore there are 5 tests contained in the test suite for the data()
method. Note we have to be careful here when we test object equality. Our $DOM
utility uses "pure" objects to store data on elements, that is, objects with null
prototype.
describe('data() method', function() {
var $el;
beforeEach(function() {
$el = $DOM('#list-1')[0];
});
// Test setting a single fragment of data
it('should set a single data fragment on the element', function() {
$el.data('x', 1);
expect($el.data('x')).toEqual(1);
});
// Test setting multiple data fragments at once
it('should set an object of data fragments on the element', function() {
$el.data({x: 5, y: 8, z: 13});
expect($el.data('x')).toEqual(5);
expect($el.data('y')).toEqual(8);
expect($el.data('z')).toEqual(13);
});
// Test getting a single fragment
it('should get a data fragment by name from the element', function() {
expect($el.data('z')).toEqual(13);
});
// Test getting all data fragments
it('should get all data fragments set on the element', function() {
var o = Object.create(null);
o.x = 5; o.y = 8; o.z = 13;
expect($el.data()).toEqual(o);
});
// Test removing all data fragments from the element
it('should remove all data fragments from the element', function() {
$el.data(null);
expect($el.data()).toEqual(Object.create(null));
});
});
At the time of writing there were 17 methods defined by the $DOM
utility.
To see tests for all the methods, download the code from GitHub (link at the top of the article).
To run all test, open the SpecRunner.html file in the Jasmine directly.
When all tests pass, we should see something like this:
Whereas when one or more of our tests fail, we see something like the image below. Jasmine shows us which particular test(s) failed and where in our code we can find the assertions which did not evaluate to true.