Universal unit testing (browser, Node.js) with Jasmine

[2011-10-18] dev, javascript, jsmodules, jslang, jstools
(Ad, please don’t block)
Update 2011-11-19: Bridging the module gap between Node.js and browsers. [With the given information, you can write your unit tests as modules that run on both platforms.]

This post gives a quick introduction to the JavaScript unit test framework Jasmine. It has the advantage of allowing tests to be run both in browsers and on Node.js.

The example project

As an example, this post uses the project jasmine-intro on GitHub. It is a directory with the following files:
    jasmine-intro/
    ├── repeat.js
    ├── repeat.runner.html
    └── repeat.spec.js
repeat.js is the code to be tested:
    if (! String.prototype.repeat) {
        String.prototype.repeat = function (times) {
            return new Array(times+1).join(this);
        }
    }
repeat.spec.js contains the tests and can be run on Node.js, repeat.runner.html loads and executes repeat.spec.js in a web browser.

Writing tests

An expectation is used to test one single assertion, such as “if I call this method in this manner then the result has to be X”. You write an expectation by calling the function expect() with a value you want to test. The returned object has matchers, methods that allow you to examine the value. See below for a list of commonly used matchers. Example:
    expect("abc".repeat(2)).toEqual("abcabc");
A spec is a sequence of expectations that tests a single functionality. You write a spec via the function
    it(description, function)
where description is a text string describing the functionality being tested and function runs the expectations. Example:
    it("repeats strings", function() {
        expect("abc".repeat(2)).toEqual("abcabc");
        expect("abc".repeat(0)).toEqual("");
    });
A suite groups related specs. You cannot write stand-alone specs. A spec is created via the function
    describe(description, function)
where description is a text string describing what entity the suite is about and function runs the specs. Example:
    describe("repeat", function() {
        it("repeats strings", function() {
            expect("abc".repeat(2)).toEqual("abcabc");
            expect("abc".repeat(0)).toEqual("");
        });
    });

Matchers

The following matchers are frequently used in Jasmine – quoting the Jasmine documentation:
expect(x).toEqual(y);compares objects or primitives x and y and passes if they are equivalent
expect(x).toBe(y);compares objects or primitives x and y and passes if they are the same object
expect(x).toMatch(pattern);compares x to string or regular expression pattern and passes if they match
expect(x).toBeDefined();passes if x is not undefined
expect(x).toBeUndefined();passes if x is undefined
expect(x).toBeNull();passes if x is null
expect(x).toBeTruthy();passes if x evaluates to true
expect(x).toBeFalsy();passes if x evaluates to false
expect(x).toContain(y);passes if array or string x contains y
expect(x).toBeLessThan(y);passes if x is less than y
expect(x).toBeGreaterThan(y);passes if x is greater than y
expect(fn).toThrow(e);passes if function fn throws exception e when executed
You negate a matcher by prepending .not:
    expect(x).not.toEqual(y);

Running tests on Node.js

In order to run Jasmine on Node.js, we only need to install the module jasmine-node via npm:
    sudo npm install -g jasmine-node
The nice thing about jasmine-node is that it also installs a command line tool for running tests. Given the following content of repeat.spec.js.
    require("./repeat.js");

    describe("repeat", function() {
        it("repeats strings", function() {
            expect("abc".repeat(2)).toEqual("abcabc");
            expect("abc".repeat(0)).toEqual("");
        });
    });
Now you can perform the above tests via
    $ jasmine-node repeat.spec.js 
    Started
    .

    Finished in 0.003 seconds
    1 test, 1 assertion, 0 failures
If invoked without an argument, the jasmine-node command looks in the current directory and all of the directories it contains for files ending with “.spec.js” and runs them.

Running tests in the browser

The following HTML runs the file repeat.spec.js in a browser.
    <!doctype html>
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
            <title>Jasmine Spec Runner: repeat</title>

            <link rel="shortcut icon" type="image/png" href="/Users/rauschma/local/jasmine/images/jasmine_favicon.png">
            <link href="/Users/rauschma/local/jasmine/lib/jasmine-core/jasmine.css" rel="stylesheet"/>
    
            <!-- Jasmine files -->
            <script type="text/javascript" src="/Users/rauschma/local/jasmine/lib/jasmine-core/jasmine.js"></script>
            <script type="text/javascript" src="/Users/rauschma/local/jasmine/src/html/TrivialReporter.js"></script>

            <!-- CUSTOMIZE: The code to be tested and the tests -->
            <script type="text/javascript" src="repeat.js"></script>
            <script>
                function require() {
                    // do nothing, allows repeat.spec.js to require a node module
                }
            </script>
            <script type="text/javascript" src="repeat.spec.js"></script>

            <script type="text/javascript">
                (function() {
                    var jasmineEnv = jasmine.getEnv();
                    ...
                    window.onload = function() {
                        ...
                        jasmineEnv.execute();
                    };
                })();
            </script>
        </head>
        <body>
        </body>
    </html>
Apart from the paths starting with
    /Users/rauschma/local/jasmine/
only the lines after
    <!-- CUSTOMIZE: ... -->
need to be changed for a given spec.js file. Opening the HTML file in a web browser looks as follows. All the tests ran successfully, which explains why there is only green and no red.

Related reading

  1. Jasmine homepage
  2. jasmine-node on GitHub