Advertisement
JavaScript & AJAX

Testing Your JavaScript with Jasmine

by

We all know we should be testing our code, but we don’t actually do it. I guess it’s fair to say that most of us put it off because, nine times out of ten, it means learning yet another concept. In this tutorial, I’ll introduce you to a great little framework for testing your JavaScript code with ease.


Step 0: Understanding BDD

Today, we’re going to be learning about the Jasmine BDD testing framework. But we’re stopping here for a detour first, to talk very briefly, about BDD and TDD. If you’re not familiar with these acronyms, they stand for Behaviour-Driven Development and Test-Driven Development. I’m in the middle of learning about what each of these is in practice and how they are different, but here are some of the basic differences:

BDD and TDD … stand for Behaviour-Driven Development and Test-Driven Development.

TDD in its simplest form is just this:

  1. Write your tests
  2. Watch them fail
  3. Make them pass
  4. Refactor
  5. Repeat

That’s pretty easy to understand, eh?

BDD is a little more complex: as I understand it right now, I don’t think that you or I as a single developer can actually practice it fully; it’s more of a team thing. Here are a few of the practices of BDD:

  • Establishing the goals of different stakeholders required for a vision to be implemented
  • Involving stakeholders in the implementation process through outside-in software development
  • Using examples to describe the behavior of the application, or of units of code
  • Automating those examples to provide quick feedback and regression testing

To learn more, you can read the extensive Wikipedia Article (from which those points were taken).

All this to say that, while Jasmine bills itself as a BDD framework, we’re going to be using it in a more TDD-style way. That doesn’t mean we’re using it wrong, though. Once we’re finished, you’ll be able to test your JavaScript with ease … and I expect you to do it!


Step 1: Learning the Syntax

Jasmine takes a lot of cues from Rspec.

If you’re at all familiar with Rspec, the de facto BDD framework, you’ll see that Jasmine takes a lot of cues from Rspec. Jasmine tests are primarily two parts: describe blocks and it blocks. Let’s see how this works.

We’ll look at some closer-to-real-life tests in a few, but for now, we’ll keep it simple:

describe('JavaScript addition operator', function () {
    it('adds two numbers together', function () {
        expect(1 + 2).toEqual(3);
    });
});

Both the describe and it functions take two parameters: a text string and a function. Most test frameworks try to read as much like English as possible, and you can see this with Jasmine. First, notice that the string passed to describe and the string passed to it form a sentence (of sorts): “JavaScript addition operator adds two numbers together.” Then, we go on to show how.

Inside that it block, you can write all the setup code you need for your test. We don’t need any for this simple example. Once you’re ready to write the actual test code, you’ll start with the expect function, passing it whatever you are testing. Notice how this forms a sentence as well: we “expect 1 + 2 to equal 3.”

But I’m getting ahead of ourselves. As I said, whatever value you pass into expect will be tested. The method you call, off the returned value of expect, will be determined by which test is run. This group of methods is called ‘matchers’, and we’ll be looking at several of them today. In this case, we’re using the toEqual matcher, which checks to see that the value passed to expect and the value passed to toEqual are the same value.

I think you’re ready to take this to the next level, so let’s set up a simple project using Jasmine.


Step 2: Setting up a Project

Jasmine can be used by itself; or you can integrate it with a Rails project. We’ll do the former. While Jasmine can run outside the browser (think Node, among other places), we can get a really nice little template with the download.

So, head on over to the standalone download page and get the latest version. You should get something like this:

Jasmine Download

You’ll find the actual Jasmine framework files in the lib folder. If you prefer to structure your projects differently, please do so; but we’re going to keep this for now.

There’s actually some sample code wired up in this project template. The “actual” JavaScript ( the code we want to test) can be found in the src subdirectory; we’ll be putting ours there shortly. The testing code—the specs—go in the spec folder. Don’t worry about the SpecHelper.js file just yet; we’ll come back to that.

That SpecRunner.html file is what runs the tests in a browser. Open it up (and check the “passed” checkbox in the upper right corner), and you should see something like this:

Sample Tests

This shows us that all the tests for the sample project are passing. Once you get through this tutorial, I recommend you open up the spec/PlayerSpec.js file and peruse that code. But right now, let’s give this test writing stuff a try.

  • Create convert.js in the src folder.
  • Create convertSpec.js in the spec folder,
  • Copy the SpecRunner.html file and rename it SpecRunner.original.html.
  • Remove the links to the sample project files in SpecRunner.html and add these lines:

    <script src="src/convert.js"><>/script>
    <script src="spec/convertSpec.js"></script>

Now we’re ready to create a mini-library that will convert between measurement units. We’ll start by writing the tests for our mini-library.


Step 3: Writing the Tests

So, let’s write our tests, shall we?

describe( "Convert library", function () {
    describe( "distance converter", function () {

        });

        describe( "volume converter", function () {

    });
});

We start with this; we’re testing our Convert library. You’ll notice that we’re nesting describe statements here. This is perfectly legal. It’s actually a great way to test seperate functionality chunks of the same codebase. Instead of two seperate describe calls for Convert library’s distance conversions and volume conversions, we can have a more descriptive suite of tests like this.

Now, onto the actual tests. I’ll repeat the inner describe calls here for your convenience.

describe( "distance converter", function () {
    it("converts inches to centimeters", function () {
        expect(Convert(12, "in").to("cm")).toEqual(30.48);
    });

    it("converts centimeters to yards", function () {
        expect(Convert(2000, "cm").to("yards")).toEqual(21.87);
    });
});

Here are our tests for distance conversions. It’s imporant to notice something here: we haven’t written a speck of code for our Convert library yet, so in these tests we’re doing more than just check to see if it works: we’re actually deciding how it will be used (and therefore implemented). Here’s how we’ve decided to make our conversions:

Convert(<number>, <from unit string>).to(<to unit string>);

Yes, I’m taking a cue from the way Jasmine has implemented its tests, but I think it’s a nice format. So, in these two tests, I’ve made the conversions myself (ok, with a calculator) to see what the results of our calls should be. We’re using the toEqual matcher to see if our tests pass.

Here’s the volume tests:

describe( "volume converter", function () {
    it("converts litres to gallons", function () {
        expect(Convert(3, "litres").to("gallons")).toEqual(0.79);
    });

    it("converts gallons to cups", function () {
        expect(Convert(2, "gallons").to("cups")).toEqual(32);
    });
});

And I’m going to add two more tests in our top-level describe call:

it("throws an error when passed an unknown from-unit", function () {
    var testFn = function () {
        Convert(1, "dollar").to("yens");
    }
    expect(testFn).toThrow(new Error("unrecognized from-unit"));
});

it("throws an error when passed an unknown to-unit", function () {
    var testFn = function () {
        Convert(1, "cm").to("furlongs");
    }
    expect(testFn).toThrow(new Error("unrecognized to-unit"));
});

These check for errors that should be thrown when unknown units are passed into either the Convert function or the to method. You’ll notice that I’m wrapping the actual conversion in a function and passing that to the expect function. That’s because we can’t call the function as the expect parameter; we need to hand it a function and let it call the function itself. Since we need to pass a parameter to that to function, we can do it this way.

The other thing to note is that I’m introducing a new matcher: toThrow, which takes an error object. We’ll look at some more matchers soon.

Now, if you open SpecRunner.html in a browser, you’ll get this:

Convert failed tests

Great! Our tests are failing. Now, let’s open our convert.js file and do some work:

function Convert(number, fromUnit) {
    var conversions = {
            distance : {
                meters : 1,
                cm     : 0.01,
                feet   : 0.3048,
                inches : 0.0254,
                yards  : 0.9144
            },
            volume : {
                liters : 1,
                gallons: 3.785411784,
                cups   : 0.236588236 
            }
        },
        betweenUnit = false,
        type, unit;

    for (type in conversions) {
        if (conversions(type)) {
            if ( (unit = conversions[type][fromUnit]) ) {
                betweenUnit = number * unit * 1000;
            }
        }
    }

    return {
        to : function (toUnit) {
            if (betweenUnit) {
                for (type in conversions) {
                    if (conversions.hasOwnProperty(type)) {
                        if ( (unit = conversions[type][toUnit]) ) {
                            return fix(betweenUnit / (unit * 1000));
                        }
                    }
                }
                throw new Error("unrecognized to-unit");
            } else {
                throw new Error("unrecognized from-unit");
            }  

            function fix (num) {
                return parseFloat( num.toFixed(2) );
            }
        }
    };
}

We’re not really going to discuss this, because we’re learning Jasmine here. But here are the main points:

  • We’re making the conversions by storing the conversion in an object; conversion numbers are classified by type (distance, volume, add your own). For each field of measurement, we have a base value (meters or liters, here) that everything converts to. So when you see yards: 0.9144, you know that that’s how many yards there are in a meter. Then, to convert yards to, say, centimeters, we multiply yards by the first parameter (to get the number of meters) and then divide the product by cm, the number of meters in a centimeter. This way, we don’t have to store the conversion rates for every pair of values. This also makes it easy to add new values later.
  • In our case, we’re expecting the units passed in to be the same as the keys we’re using in the conversion “table.” If this were a real library, we’d want to support multiple formats—like ‘in’, ‘inch’, and ‘inches’—and therefore we’d want to add some logic to match the fromUnit to the right key.
  • At the end of the Convert function, we store the intermediate value in betweenUnit, which is initialized to false. That way, if we don’t have the fromUnit, betweenUnit will be false going into the to method, and so an error with be thrown.
  • If we don’t have the toUnit, a different error will be thrown. Otherwise, we’ll divide as neccessary and return the converted value.

Now, go back to SpecRunner.html and reload the page. You should now see this (after checking “Show passed”):

There you go! Our tests are passing. If we were developing a real project here, we would write tests for a certain chunk of functionality, make them pass, write tests for another check, make them passs, etc. But since this was a simple example, we’ve just done it all in one fell swoop.

And now that you’ve seen this simple example of using Jasmine, let’s look at a few more features that it offers you.


Step 4: Learning the Matchers

So far, we’ve used two matchers: toEqual and toThrow. There are, of course, many others. Here are a few you’ll probably find useful; you can see the whole list on the wiki.

toBeDefined / toBeUndefined

If you just want to make sure a variable or property is defined, there’s a matcher for that. There’s also one to confirm that a variable or property is undefined.

it("is defined", function () {
    var name = "Andrew";
    expect(name).toBeDefined();
})

it("is not defined", function () {
    var name;
    expect(name).toBeUndefined();
});

toBeTruthy / toBeFalsy

If something should be true or false, these matchers will do it.

it("is true", function () {
    expect(Lib.isAWeekDay()).toBeTruthy();
});
it("is false", function () {
    expect(Lib.finishedQuiz).toBeFalsy();
});

toBeLessThan / toBeGreaterThan

For all you number people. You know how these work:

it("is less than 10", function () {
    expect(5).toBeLessThan(10);
});
it("is greater than 10", function () {
    expect(20).toBeGreaterThan(10);
});

toMatch

Have some output text that should match a regular expression? The toMatch matcher is ready and willing.

it("outputs the right text", function () {
    expect(cart.total()).toMatch(/\$\d*.\d\d/);
});

toContain

This one is pretty useful. It checks to see if an array or string contains an item or substring.

it("should contain oranges", function () {
    expect(["apples", "oranges", "pears"]).toContain("oranges");
});

There are a few other matchers, too, that you can find in the wiki. But what if you want a matcher that doesn’t exist? Really, you should be able to do just about anything with some set-up code and the matchers Jasmine provides, but sometimes it’s nicer to abstract some of that logic to have a more readable test. Serendipitously (well, actually not), Jasmine allows us to create our own matchers. But to do this, we’ll need to learn a little something else first.

Step 5: Covering Before and After

Often—when testing a code base—you’ll want to perform a few lines of set-up code for every test in a series. It would be painful and verbose to have to copy that for every it call, so Jasmine has a handy little feature that allows us to designate code to run before or after each test. Let’s see how this works:

describe("MyObject", function () {
    var obj = new MyObject();

    beforeEach(function () {
        obj.setState("clean");
    });

    it("changes state", function () {
        obj.setState("dirty");
        expect(obj.getState()).toEqual("dirty");
    })
    it("adds states", function () {
        obj.addState("packaged");
        expect(obj.getState()).toEqual(["clean", "packaged"]);
    })
});

In this contrived example, you can see how, before each test is run, the state of obj is set to “clean”. If we didn’t do this, the changed made to an object in a previous test persist to the next test by default. Of course, we could also do something similar with the AfterEach function:

describe("MyObject", function () {
    var obj = new MyObject("clean"); // sets initial state

    afterEach(function () {
        obj.setState("clean");
    });

    it("changes state", function () {
        obj.setState("dirty");
        expect(obj.getState()).toEqual("dirty");
    })
    it("adds states", function () {
        obj.addState("packaged");
        expect(obj.getState()).toEqual(["clean", "packaged"]);
    })
});

Here, we’re setting up the object to begin with, and then having it corrected after every test. If you want the MyObject function so you can give this code a try, you can get it here in a GitHub gist.

Step 6: Writing Custom Matchers

Like we said earlier, customer matchers would probably be helpful at times. So let’s write one. We can add a matcher in either a BeforeEach call or an it call (well, I guess you could do it in an AfterEach call, but that wouldn’t make much sense). Here’s how you start:

beforeEach(function () {
    this.addMatchers({

    });
});

Pretty simple, eh? We call this.addMatchers, passing it an object parameter. Every key in this object will become a matcher’s name, and the associated function (the value) will be how it is run. Let’s say we want to create a matcher that with check to see if one number is between two others. Here’s what you’d write:

beforeEach(function () {
    this.addMatchers({
        toBeBetween: function (rangeFloor, rangeCeiling) {
            if (rangeFloor > rangeCeiling) {
                var temp = rangeFloor;
                rangeFloor = rangeCeiling;
                rangeCeiling = temp;
            }
            return this.actual > rangeFloor && this.actual < rangeCeiling;
        }
    });
});

We simply take two parameters, make sure the first one is smaller than the second, and return a boolean statement that evaluates to true if our conditions are met. The important thing to notice here is how we get a hold of the value that was passed to the expect function: this.actual.

it("is between 5 and 30", function () {
    expect(10).toBeBetween(5, 30);
});

it("is between 30 and 500", function () {
    expect(100).toBeBetween(500, 30);
});

This is what the SpecHelper.js file does; it has a beforeEach call that adds the matcher tobePlaying(). Check it out!


Conclusion: Having Fun Yourself!

There’s a lot more you can do with Jasmine: function-related matchers, spies, asynchronous specs, and more. I recommend you explore the wiki if you’re interested. There are also a few accompanying libraries that make testing in the DOM easier: Jasmine-jQuery, and Jasmine-fixture (which depends on Jasmine-jQuery).

So if you aren’t testing your JavaScript so far, now is an excellent time to start. As we’ve seen, Jasmine's fast and simple syntax makes testing pretty simple. There’s just no reason for you not to do it, now, is there?

Related Posts
  • Code
    JavaScript & AJAX
    Testing in Node.jsNodejs testing chai retina preview
    A test driven development cycle simplifies the thought process of writing code, makes it easier, and quicker in the long run. But just writing tests is not enough by itself, knowing the kinds of tests to write and how to structure code to conform to this pattern is what it's all about. In this article we will take a look at building a small app in Node.js following a TDD pattern.Read More…
  • Code
    News
    Write for Wptuts+Writeforus 400
    Our focus here on Wptuts+ is on tutorials for WordPress developers (makes sense, right?). We cover anything related to WordPress, in particular theme and plugin development. Our tutorials are aimed at anyone from beginner to intermediate, intermediate to advanced, and so on. Ultimately, our goal is to make sure that the reader leaves with more knowledge than when they arrived. What We’re Looking For At the moment we’re wanting to publish more content on areas such as responsive theme development, app themes, unit testing with WordPress, preprocessors (Sass / LESS, in particular), parallax implementation, and a variety of plugin examples. Basic tutorials are welcome, but we’re really after thorough coverage of these areas. Think Tom McFarlin-style (check out Tom’s series’ on Wptuts+ to get what I mean there). At the moment we only accept content in English. We’ll proofread, edit, and provide feedback on your content, but we won’t re-write it for you, so it’s important your English is fluent. Having said that, if you have a question about how to phrase something in English, go ahead and write it as best you can and one of the editors will be happy to provide feedback prior to publishing the article. Don’t let intimidation of the English language prevent you from contributing an article! I’m going to go ahead and assume you know a little about WordPress for this, so you’ll be comfortable drafting your posts in WordPress ;) Let’s Talk Money We don’t expect you to write for us for free! You’ll start out on $150 USD per tutorial, and we bump that up once you’ve written a handful of posts for us. High profile authors can negotiate a higher rate with us, but you’ve really got to know your stuff. I’m talking serious WordPress chops! Some of our regular authors invoice us for $750+ USD per month. That’s not bad for dropping some knowledge and getting some recognition, too! For the right authors, we can guarantee you publishing spots to help you pump out quality WordPress tutorials. Also, who doesn’t love a marketplace badge? You get a special badge for contributing a tutorial to a Tuts+ site ;) Where To Next? Whether you’ve written for Wptuts+ before, or you’re fresh to this tutorial writing business, please hit me up! (Even if you’ve applied before and it hasn’t worked out!) Shoot an email to wp@tutsplus.com with the following info: Name Marketplace username Brief paragraph filling me in on your background and why you want to write for us One link to a tutorial you’ve written, helped with, or is the kind of content you want to create Two ideas for tutorials you’d like to write for us A link to your portfolio and / or GitHub profile Show your passion! Best of luck! I look forward to hearing from you and hopefully working together. If WordPress isn’t your strong suit, don’t forget there are other Tuts+ sites you may consider getting in touch with too: Nettuts+ Webdesigntuts+ Mobiletuts+ Gamedevtuts+ Psdtuts+ Vectortuts+ Audiotuts+ Aetuts+ Cgtut+ Phototuts+ Mactuts+ Crafttuts+ Read More…
  • Code
    PHP
    How to Accept Payments With StripeCode
    Processing credit cards is unfortunately far more difficult than we might hope, as developers. Given that it’s such a common task, is it really necessary that we jump through countless hoops (surrounded by fire, of course) for the sole purpose of processing a payment? Merchants? Gateways? SSL? Security? Very quickly, a seemingly simple operation can become an overwhelmingly confusing and, more importantly, dangerous task. Any time that you find yourself handling a user’s sensitive data, you better be on your toes. Read More…
  • Code
    Tools
    Your Obedient Assistant: YeomanCode
    One thing is certain: times sure have changed. Not too long ago, front-end development, though not simple, was manageable. Learn HTML, a bit of CSS, and you’re on your way. These days, however, for lack of better words, there are far more variables to juggle. Preprocessors, performance tuning, testing, image optimization, and minification represent but just a few of the key factors that the modern day front-end developer must keep in mind.Read More…
  • Code
    JavaScript & AJAX
    Better CoffeeScript Testing With MochaMocha coffeescript
    Recently, I’ve been doing a considerable amount of CoffeeScript work. One problem I ran into early-on was testing: I didn’t want to manually convert my CoffeeScript to JavaScript before I could test it. Instead, I wanted to test from CoffeeScript directly. How’d I end up doing it? Read on to find out!Read More…
  • Code
    JavaScript & AJAX
    Build Your First JavaScript LibraryYour first js library
    Ever marvelled at the magic of Mootools? Ever wondered how Dojo does it? Ever been curious about jQuery's gymnastics? In this tutorial, we’re going to sneak behind the scenes and try our hand at building a super-simple version of your favorite library.Read More…