Advertisement

JavaScript Testing from Scratch

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

This likely isn’t the first tutorial on testing that you’ve ever seen. But perhaps you’ve had your doubts about testing, and never took the time to read them. After all, it can seem like extra work for no reason.

This tutorial intends to change your views. We’re going to start at the very beginning: what is testing and why should you do it? Then, we’ll talk briefly about writing testable code, before actually, you know, doing some testing! Let’s get to it!


Prefer A Screencast?

Part 1


Part 2


Part 3



Defining Testing

Quite simply, testing is the idea of having a set of requirements that a given piece of code must pass to be robust enough to be used in the real world. Often, we’ll write some JavaScript and then open it up in the browser and click around a bit to make sure everything is working as we would expect. Although that is sometimes necessary, that’s not the type of testing we’re talking about here. In fact, I hope that this tutorial will convince you that quick-and-dirty self testing should complement a more rigid testing procedure: self-testing is fine, but a thorough list of requirements is paramount.


Reasons for Testing

As you might guess, the problem with the refresh-and-click-around JavaScript testing is twofold:

  1. We might not remember to check something; even if we do, we might not re-check then after code tweaks.
  2. There may be some parts of the code that aren’t really testable that way.

By writing tests that check everything your code should do, you can verify that your code is in the best of shape before actually using it on a website. By the time something is actually running in a browser, there are probably multiple points of failure. Writing tests allows you to focus on every testable part individually; if each piece does its job right, things should work together without issue (testing individual parts like this is called unit testing).


Writing Testable Code

If you’re a programming polyglot), you might have done testing in other languages. But I’ve found testing in JavaScript a different beast to slay. After all, you aren’t building too many user interfaces in, say, PHP or Ruby. Often, we’re doing DOM work in JavaScript, and how exactly do you test that?

Well, the DOM work isn’t what you want to write tests for; it’s the logic. Obviously, then, the key here is to separate your logic and your UI code. This isn’t always ease; I’ve written my fair share of jQuery-powered UI, and it can get pretty messy pretty quickly. Not only does this make it hard to test, but intertwined logic and UI code can also be hard to modify when the desired behaviour changes. I’ve found using methodologies like templates (also, templates) and pub/sub (also, pub/sub) make writing better, more testable code easier.

One more thing, before we start coding: how do we write our tests? There are numerous testing libraries that you can use (and many good tutorials to teach you to use them; see the links as the end). However, we’re going to build a small testing library from scratch. It won’t be as fancy as some libraries, but you’ll get to see exactly what is going on.

With this in mind, let’s get to work!


Building a Test Mini-Framework

We’re going to be building a micro photo gallery: a simple list of thumbnails, with one image displaying full-size above them. But first, let’s build out testing function.

As you learn more about testing and testing libraries, you’ll find numerous testing methods for testing all sorts of a specifics. However, it can all be boiled down to whether two things are equal or not: for example,

  • Is the value returned from this function equal to what we expected to get back?
  • Is this variable of the type we expected it to be?
  • Does this array have the expected number of items?

So, here’s our method to test for equality:

var TEST = { 
    areEqual: function (a, b, msg) { 
        var result = (a === b); 
        console.log( (result ? "PASS: " : "FAIL: ") + msg ); 
        return result; 
    } 
};

It’s pretty simple: the method takes three parameter. The first two are compared, and if they are equal, the tests passes. The third parameter is a message describing the test. In this simple test library, we’re just outputting our tests to the console, but you can create HTML output with appropriate CSS styling if you’d like.

Here’s the areNotEqual method (within the same TEST object):

areNotEqual: function (a,b, msg) { 
    var result = (a !== b); 
    console.log( (result ? "PASS: " : "FAIL: ") + msg ); 
    return result; 
}

You’ll notice the last two lines of areEqual and areNotEqual the same. So, we can pull those out like this:

var TEST = { 
    areEqual: function (a, b, msg) { 
        return this._output(a === b, msg) 
    }, 
    areNotEqual: function (a,b, msg) { 
        return this._output(a !== b, msg); 
    }, 
    _output : function (result, msg) { 
        console[result ? "log" : "warn"]( (result ? "PASS: " : "FAIL: ") + msg ); 
        return result; 
    } 
};

Great! Neat thing here is that we can add other “sugar” methods using the methods we’ve already written:

TEST.isTypeOf = function (obj, type, msg) { 
    return this.areEqual(typeof obj, type, msg); 
}; 
 
TEST.isAnInstanceOf = function (obj, type, msg) { 
    return this._output(obj instanceof type, msg); 
} 
 
TEST.isGreaterThan = function (val, min, msg) { 
    return this._output(val > min, msg); 
}

You can experiment with this on your own; after going through this tutorial, you’ll have a good idea of how to use it.


Preparing for our Gallery

So, let’s create a super-simple photo gallery, using our mini TEST framework to create some tests. I’ll mention here that while test-driven development is a great practice, we won’t be using it in this tutorial, primarily because it isn’t something you can learn in a single tutorial; it takes a lot of practice to really grok. When you’re starting out, it’s easier to write a little bit of code and then testing it.

So, let’s start. Of course, we’ll need some HTML for our gallery. We’ll keep this pretty basic:

 
<!DOCTYPE html> 
<html> 
  <head> 
    <title> Testing in JavaScript </title> 
    <link rel="stylesheet" href="gallery.css" /> 
  </head> 
  <body> 
    <section id="gal-1" class="gallery"> 
      <img src="images/road.jpg" /> 
      <ul> 
        <li><img src="images/apple-thumb.jpg" /></li> 
        <li><img src="images/bikes-thumb.jpg" /></li> 
        <li><img src="images/bridge-thumb.jpg" /></li> 
        <li><img src="images/post-thumb.jpg" /></li> 
        <li><img src="images/road-thumb.jpg" /></li> 
      </ul> 
    </section> 
 
    <script src="test.js"></script> 
    <script src="gallery.js"></script> 
    <script src="gallery-test.js"></script> 
  </body> 
</html>

There are two main things worth noticing here: first, we’ve got a <section> that holds the very simple markup for our image gallery. No, it’s probably not very robust, but it gives us something work with. Then, notice that we are hooking up three <script>s: one is our little test library, as found above. One is the gallery we will create. The final one holds the tests for our gallery. Notice also the paths to the images: the thumbnail filenames have “-thumb” appended to them. That’s how we’ll find the larger version of the image.

I know you’re itching to get coding, so throw this in a gallery.css file:

.gallery { 
    background: #ececec; 
    overflow: hidden; 
    width: 620px; 
} 
.gallery > img { 
    margin: 20px 20px 0; 
    padding: 0; 
} 
.gallery ul { 
    list-style-type: none; 
    margin: 20px; 
    padding: 0; 
    overflow:hidden; 
} 
.gallery li { 
    float:left; 
    margin: 0 10px; 
} 
.gallery li:first-of-type { 
    margin-left: 0px; 
} 
.gallery li:last-of-type { 
    margin-right: 0px; 
}

Now, you can load this up in your browser and see something like this:

OKAY ALREADY! Let’s write some JavaScript, shall we?


Outlining our Gallery

Open up that gallery.js file. Here’s how we start:

var Gallery = (function () { 
    var Gallery = {}, 
     
    galleryPrototype = { 
    }; 
 
    Gallery.create = function (id) { 
        var gal = Object.create(galleryPrototype); 
         
        return gal; 
    }; 
    return Gallery; 
}());

We’ll be adding more, but this is a good start. We’ve using a self-invoking anonymous function (or an immediately-invoked function expression) to keep everything together. Our “internal” Gallery variable will be returned and be the value of our public Gallery variable. As you can see, calling Gallery.create will create a new gallery object with Object.create. If you aren’t familiar with Object.create, it just creates a new object using the object that you pass it as the new object’s prototype (it’s pretty browser compliant, too). We’ll be filling in that prototype, and adding to our Gallery.create method as well. But, now let’s write our first test:

var gal = Gallery.create("gal-1"); 
 
TEST.areEqual(typeof gal, "object", "Gallery should be an object");

We start by creating an “instance” of Gallery; then, we run a test to see if the value returned is an object.

Put these two lines in our gallery-test.js; now, open our index.html page in a browser and pop open a JavaScript console. You should see something like this:


Great! Our first test is passing!


Writing the Constructor

Next, we’ll fill in our Gallery.create method. As you’ll see, we aren’t worrying about making this example code super-robust, so we’ll be using some things that aren’t compatible in every browser ever created. Namely, document.querySelector / document.querySelectorAll; also, we’ll only be using modern browser event handling. Feel free to substitute your favourite library if you’d like.

So, let’s start with some declarations:

var gal     = Object.create(galleryPrototype), ul, i = 0, len; 
gal.el      = document.getElementById(id); 
ul          = gal.el.querySelector("ul"); 
 
gal.imgs          = gal.el.querySelectorAll("ul li img"); 
gal.displayImage = gal.el.querySelector("img:first-child"); 
gal.idx          = 0; 
gal.going        = false; 
gal.ids          = [];

Four variables: notably, our gallery object and the gallery’s <ul> node (we’ll use i and len in a minute). Then, six properties on our gal object:

  • gal.el is the “root” node on our gallery’s markyp.
  • gal.imgs is a list of the <li>s that hold our thumbnails.
  • gal.displayImage is the large image in our gallery.
  • gal.idx is the index, the current image being viewed.
  • gal.going is a boolean: it’s true if the gallery is cycling through the images.
  • gal.ids will be a list of the IDs for the images in our gallery. For example, if the thumbnail is named “dog-thumb.jpg”, then “dog” is the ID and “dog.jpg” is the display size image.

Notice that DOM elements have querySelector and querySelectorAll methods as well. We can use gal.el.querySelector to ensure that we’re only selecting elements within this gallery’s markup.

Now, fill in gal.ids with the image IDs:

len = gal.imgs.length; 
 
for (; i < len; i++ ) { 
    gal.ids[i] = gal.imgs[i].getAttribute("src").split("-thumb")[0].split("/")[1];  
}

Pretty straight-forward, right?

Finally, let’s wire up an event handler on the <ul>.

ul.addEventListener("click", function (e) { 
    var i = [].indexOf.call(gal.imgs, e.target); 
    if (i > -1) { 
        gal.set(i); 
    } 
}, false);

We start by checking to see if the lowest element that received the click (e.target; we’re not worrying about oldIE support here) is in our list of images; since NodeLists don’t have an indexOf method, we’ll use the array version (If you aren’t familiar with call and apply in JavaScript, see our quick tip on that subject.). If that’s greater than -1, we’ll pass it to gal.set. We haven’t written this method yet, but we’ll get to it.

Now, let’s flip back to our gallery-test.js file and write some tests to make sure our Gallery instance has the right properties:

TEST.areEqual(gal.el.id, "gal-1", "Gallery.el should be the one we specified"); 
 
TEST.areEqual(gal.idx, 0, "Gallery index should start at zero");

Our first test verifies that our gallery constructor found the right element. The second test verifies that the index begins at 0. You could probably write a bunch of tests to verify that we have the right properties, but we’ll be writing tests for the methods that will use these properties, so that won’t really be necessary.


Building the Prototype

Now, let’s move back to that galleryPrototype object that is currently empty. This is where we will house all the methods of our Gallery “instances.” Let’s start with the set method: this is the most imporant method, because it’s the one that actually changes the displayed image. It takes either the index of the image or the id string of the image.

// within `galleryProtytype` 
set: function (i) { 
    if (typeof i === &#39;string&#39;) { 
        i = this.ids.indexOf(i); 
    } 
    this.displayImage.setAttribute("src", "images/" + this.ids[i] + ".jpg"); 
    return (this.idx = i); 
}

If the method gets the ID string, it will find the correct index number for that ID. Then, we set the displayImage’s src to the correct image path, and return the new current index while setting it as the current index.

Now, let’s tests that method (back in gallery-test.js):

TEST.areEqual(gal.set(4), 4, "Gallery.set (with number) should return the same number passed in"); 
 
TEST.areEqual(gal.displayImage.getAttribute("src"), "images/road.jpg", "Gallery.set (with number) should change the displayed image"); 
 
TEST.areEqual(gal.set("post"), 3, "Gallery.set (with string) should move to appropriate image"); 
 
TEST.areEqual(gal.displayImage.getAttribute("src"), "images/post.jpg", "Gallery.set (with string) should change the displayed images");

We test our test method with both a number and a string parameter for set. In this case, we’re able to check the src for the image and make sure the UI is adjusted accordingly; it’s not always possible or necessary to make sure what the user is seeing is responding appropriately (without using something like this); that’s where the click-around type of testing is useful. However, we can do it here, so we will.

Those tests should pass. You should also be able to click on the thumbnails and have the larger versions displayed. Lookin’ good!

So, let’s move on to some methods that move between the images. These could be useful if you wanted to have “next” and “previous” buttons to cycle through the images (we won’t have these buttons, but we’ll put in the supporting methods).

For the most part, moving to the next and previous images isn’t a difficult thing. The tricky parts are going to the next image when you’re at the last one, or the previous one when you’re at the first.

// inside `galleryPrototype` 
next: function () { 
    if (this.idx === this.imgs.length - 1) { 
        return this.set(0); 
    } 
    return this.set(this.idx + 1); 
}, 
prev: function () { 
    if (this.idx === 0) { 
        return this.set(this.imgs.length - 1); 
    } 
    return this.set(this.idx - 1); 
}, 
curr: function () { 
    return this.idx; 
},

Okay, so it’s actually not too tricky. Both these methods are “sugar” methods for using set. If we’re at the last image (this.idx === this.imgs.length -1), we set(0). If we’re at the first (this.idx === 0), we set(this.imgs.length -1). Otherwise, just add or subtract one from the current index. Don’t forget that we’re returning exactly what is returned from the set call.

We also have the curr method, too. It’s not complicated at all: it just returns the current index. We’ll test it a bit later.

So, let’s tests these methods.

TEST.areEqual(gal.next(), 4, "Gallery should advance on .next()"); 
 
TEST.areEqual(gal.prev(), 3, "Gallery should go back on .prev()");

These come after our previous tests, so 4 and 3 are the values that we would expect. And they pass!

There’s only one piece left: that’s the automatic photo cycling. We want to be able to call gal.start() being playing through the images. Of course, they’ll be a gal.stop() method.

// inside `galleryPrototype` 
start: function (time) { 
    var thiz = this; 
    time = time || 3000; 
    this.interval = setInterval(function () { 
        thiz.next(); 
    }, time); 
    this.going = true; 
    return true; 
}, 
stop: function () { 
    clearInterval(this.interval); 
    this.going = false; 
    return true; 
},

Our start method will take parameter: the number of milliseconds that one image is displayed; if no parameter is given, we default to 3000 (3 seconds). Then, we simply use setInterval on a function that will call next at the appropriate time. Of course, we can’t forget to set this.going to true. Finally, we return true.

stop isn’t too difficult. Since we saved the interval as this.interval, we can use clearInterval to end it. Then, we set this.going to false and return true.

We’ll have two handy function to let know if the gallery is looping:

isGoing: function () { 
    return this.going; 
}, 
isStopped: function () { 
    return !this.going; 
}

Now, let’s test this functionality.

gal.set(0); 
 
TEST.areEqual(gal.start(), true, "Gallery should being looping"); 
 
TEST.areEqual(gal.curr(), 0, "Current image index should be 0"); 
 
setTimeout(function () { 
 
    TEST.areEqual(gal.curr(), 1, "Current image index should be 1"); 
 
    TEST.areEqual(gal.isGoing(), true, "Gallery should be going"); 
 
    TEST.areEqual(gal.stop(), true, "Gallery should be stopped"); 
 
    setTimeout(function () { 
 
        TEST.areEqual(gal.curr(), 1, "Current image should still be 1"); 
 
        TEST.areEqual(gal.isStopped(), true, "Gallery should still be stopped"); 
 
    }, 3050); 
}, 3050);

It’s a bit more complicated than our previous sets of tests: We start by using gal.set(0) to make sure we’re starting at the beginning. Then, we call gal.start() to start the looping. Next, we test that gal.curr() returns 0, meaning we’re still viewing the first image. Now we’ll use a setTimeout to wait 3050ms (just a little more than 3 seconds) before continuing our tests. Inside that setTimeout, we’ll do another gal.curr(); the index should now be 1. Then, we’ll test that gal.isGoing() is true. Next, we’ll stop the gallery gal.stop(). Now we use another setTimeout to wait another almost 3 seconds; if the gallery has really stopped, the image won’t be looping, so gal.curr() should still be 1; that’s what we test inside the timeout. Finally, we make sure our isStopped method is working.

If these tests have passed, congrats! We’ve completed our Gallery and its tests.


Conclusion

If you haven’t tried testing before, I hope that you’ve seen how simple testing can be in JavaScript. As I mentioned at the beginning of this tutorial, good testing will likely require you to write your JavaScript a little differently than you might be used to. However, I’ve found that easily testable JavaScript is also easily maintainable JavaScript.

I’ll leave you with several links that you might find useful as you go forth and write good JavaScript and good tests.

Advertisement