Spying with Jasmine

I really like using Jasmine to write unit tests in JavaScript. It’s easy to use, and the way the expects are written feels really natural. I’ve made a lot of use of Spies in tests I’ve written recently, and they’re also pretty awesome. I’ve used them in a few different ways:
* As a basic mock object – to verify that a method is being called or not called
* To control the value that’s being returned
* To fire a callback function
* To check the arguments that are being passed to a method are what I expect

Of course you could do all of this by hand crafting mock objects, but using the Jasmine spies usually means there is less code, and the tests become far more readable. The other advantage to using Spies is that Jasmine will remove the spies at the end of the tests, so it avoids the problem we had previously of mocks in one test overwriting real objects that we needed for another test.

Verifying a method is called or not

There are two ways to create a spy in Jasmine:
* spyOn
* jasmine.createSpy

spyOn can only be used when the method already exists on the object, whereas jasmine.createSpy will return a function.

Take this example of an object that would take a JQuery element and toggle the display property:

var Toggleable = function(element) {

    this.toggle = function() {
        if (currentlyVisible()) {
            hide();
        } else {
            show();
        }
    }



    function currentlyVisible() {
        return element.css("display") === "block";
    }

    function show() {
        element.show();
    }

    function hide() {
        element.hide();
    }

}

When testing the toggle function, I could use spyOn with a real JQuery object, or jasmine.createSpy to create a fake one. Below are two different ways to write a test that verifies that the jquery show method is called on the object if it’s currently hidden.

it("can create a method for you", function() {

    var fakeElement = {};
    fakeElement.css = function() {
    };
    fakeElement.show = jasmine.createSpy("Show spy");

    var toggleable = new Toggleable(fakeElement);

    toggleable.toggle();

    expect(fakeElement.show).toHaveBeenCalled();

});

it("can spy on an existing method", function() {

    var fakeElement = $("<div style='display:none'></div>");
    spyOn(fakeElement, 'show');

    var toggleable = new Toggleable(fakeElement);

    toggleable.toggle();

    expect(fakeElement.show).toHaveBeenCalled();

});

I could also use spies to verify that a method hasn’t been called. For example, I could add another expect to the second test above:

it("can tell you when a method has - and hasn't - been called", function() {

    var fakeElement = $("<div style='display:none'></div>");
    spyOn(fakeElement, 'show');
    spyOn(fakeElement, 'hide');

    var toggleable = new Toggleable(fakeElement);

    toggleable.toggle();

    expect(fakeElement.show).toHaveBeenCalled();
    expect(fakeElement.hide).not.toHaveBeenCalled();

});

Using a Spy to control the return value

In the first test above, I had to stub the css function since that was also being called in the toggle() function. I could also use a spy for this – although I don’t care whether or not it’s called, it might make the code slightly more readable. Here’s the same test with the two spies:

it("can create a method for you", function() {
	
    var fakeElement = {};
    fakeElement.css = jasmine.createSpy("CSS spy").andReturn("none");
    fakeElement.show = jasmine.createSpy("Show spy");

    var toggleable = new Toggleable(fakeElement);

    toggleable.toggle();

    expect(fakeElement.show).toHaveBeenCalled();

});

There isn’t a really nice way to return different values depending on the arguments that are passed into a spy, but it is possible using andCallFake(). Here’s the same test (again, sorry, it’s getting a bit boring now, isn’t it?) rewritten with andCallFake:

it("can create a method for you with some logic", function() {
	
    var fakeElement = {};
    fakeElement.css = jasmine.createSpy("CSS spy").andCallFake(function(property) {
    	if (property === "display") {
        	return "none";
        }
    });

    fakeElement.show = jasmine.createSpy("Show spy");

    var toggleable = new Toggleable(fakeElement);

    toggleable.toggle();

    expect(fakeElement.show).toHaveBeenCalled();

});

Firing a Callback

Quite regularly, I want to unit test methods that call out to another method and pass it a callback, for example, if it’s making an ajax request and setting the html of an element based on what’s returned.

var DataContainer = function(element) {

	this.loadData = function() {
		$.ajax({
			url: 'http://postposttechnical.com/',
			context: document.body,
			success: putDataInElement
		});
	}

	function putDataInElement(data) {
		element.html(data);
	}

}

In this case, I could use a spy to call the callback, using andCallFake, then use toHaveBeenCalled to spy on the method in the callback that I’m interested in:

it("can call a callback that's passed", function() {

    var fakeElement = {};
    fakeElement.html = jasmine.createSpy("html for fake element");

    var container = new DataContainer(fakeElement);

    var fakeData = "This will be the new html";
    $.ajax = jasmine.createSpy().andCallFake(function(params) {
        params.success(fakeData);
    });

    container.loadData();

    expect(fakeElement.html).toHaveBeenCalled();
});

The ajax spy will be passed the callback that sets the html, and pass it our fake data. Here’s a pretty contrived example that would make sure we were setting the html of the element, and not the text. It might be more useful if you have if / else branches in the code, to validate that the correct methods are being called.

it("can tell if a method has been called or not", function() {

    var fakeElement = {};
    fakeElement.html = jasmine.createSpy("html for fake element");
    fakeElement.text = jasmine.createSpy("text for fake element");

    var container = new DataContainer(fakeElement);

    var fakeData = "This will be the new html";
    $.ajax = jasmine.createSpy().andCallFake(function(params) {
        params.success(fakeData);
    });

    container.loadData();

    expect(fakeElement.html).toHaveBeenCalled();
    expect(fakeElement.text).not.toHaveBeenCalled();
});

Verifying Arguments

In the example above, I might accidentally forget to pass the data into the element.html() call. We can also test for that using the Jasmine function toHaveBeenCalledWith.

Here’s how that might work in the test above:

it("can tell if a method has been called or not and check the parameters", function() {

    var fakeElement = {};
    fakeElement.html = jasmine.createSpy("html for fake element");
    fakeElement.text = jasmine.createSpy("text for fake element");

    var container = new DataContainer(fakeElement);

    var fakeData = "This will be the new html";
    $.ajax = jasmine.createSpy().andCallFake(function(params) {
    	params.success(fakeData);
    });

    container.loadData();

    expect(fakeElement.html).toHaveBeenCalledWith(fakeData);
});

toHaveBeenCalledWith will return true if there has been at least one call with the matching arguments, even if there is more than one. To use this, you must supply all of the arguments in the right order – which may not always be possible. Consider if I wanted to check that the ajax call was made – the success callback is an internal function that I can’t create here. However, I can use jasmine.Any instead – here’s how it works:

it("can check the arguments passed to a function", function() {

    var fakeElement = {};
    fakeElement.html = jasmine.createSpy("html for fake element");

    var container = new DataContainer(fakeElement);

    var fakeData = "This will be the new html";
    $.ajax = jasmine.createSpy("Ajax Spy").andCallFake(function(params) {
        params.success(fakeData);
    });

    container.loadData();

    expect($.ajax).toHaveBeenCalledWith({
        url : 'http://postposttechnical.com/',
        context : document.body,
        success : jasmine.any(Function)
    });

});

All of the code examples here can be found on Github.

Dynamic dependencies in Jasmine

Despite having worked with JavaScript on most projects, and also unit tested it in the past, I’ve been a bit thrown by the issues I’ve been experiencing recently. I’ve been working on a fairly large existing JavaScript codebase – with no unit tests – to try and bring it under test. We chose to use Jasmine, a BDD testing framework that superseded JSpec. It’s proven to be easy to get to grips with and very pleasant to use, however, as our unit test suite has grown, I’ve tripped over on the dynamic nature of JavaScript.

Jasmine tests are divided into ‘describe’ blocks, which can be nested. Each describe block can have a ‘beforeEach’, however, any code that runs in the beforeEach – and indeed, in any of the tests – can affect the later ones running in the same suite. In particular: creating stubs.

The code we’re writing tests around has a high number of dependencies within it, which means we’ve needed to create rather a lot of stubs for each test. So what happens if I create a stub of an object for one test, then later try to run a test against the real object? It runs against the stub.

Supposing I had a Bookshelf class that looked like this:

var MyLibrary = {};

MyLibrary.BookShelf = function() {

    var books = [];

    this.addBook = function (isbn) {
        books.push(new MyLibrary.Book(isbn));
    }

    this.findBooksBy = function (author) {
        var i,
            matchingBooks = [];

        for(i=0; i<books.length; i++) {
            var book = books[i];
            if (book.isWrittenBy(author)) {
                matchingBooks.push(books)
            }
        }

        return matchingBooks;
    }

};

And supposing my Book class did something when it initialised itself that I didn’t want it to do within the test:

MyLibrary.Book = function(isbn) {
    var _title;
    var _author;
    var _isbn = isbn;

    this.isWrittenBy = function(author) {
        return author == _author;
    }

    var init = function() {
        $.getJSON("/BookDetails?isbn=" + _isbn, function(data) {
            _author = data.author;
            _title = data.title;
        });
    };

    init();
};

I could stub the Book to avoid the init function from running within my bookshelf tests:


describe("Bookshelf", function() {

    it("should find all the books by a given author", function() {

        MyLibrary.Book = function() {
            this.isWrittenBy = function() {return true;}
        };

        var shelf = new MyLibrary.BookShelf();
        shelf.addBook("1234");

        var books = shelf.findBooksBy("somebody");

        expect(books.length).toBe(1);

    });

});

Then I might want to write a test for the Book class. That’s tricky … but I could override the JQuery function:

describe("Book", function() {

    it("should return false if the author does not match", function() {

        $.getJSON = function(url, callback) {
            callback({ author: "Jo Cranford", title: "Post Post Technical" });
        }

        var myBook = new MyLibrary.Book("1234")

        expect(myBook.isWrittenBy("Not Jo Cranford")).toBe(false);
    });

});

If I now run the Bookshelf test followed by the Book test, the book test will fail, because it’s been redefined in my first test.

This may seem obvious, and in this rather contrived example, very easy to fix – but it’s also very easy to overlook, especially if the stub is coming from another file. With a team of several developers, code with lots of dependencies, and a suite of tests that’s growing fairly quickly, it can cause quite a bit of annoyance!

We’ve solved the problem for now by adapting the build to run each spec file in its own sandbox. Each spec file tests one object, and so far this approach is working. However, it does cause the build to run slower, which is fine right now since Jasmine is so fast and we don’t have that many tests, but it may become more painful in the future.

The longer term solution is of course to avoid writing code, even JavaScript code, with nasty dependencies and use Dependency Injection instead :)

Example code is on github here.

Testing JavaScript with JSpec

I was recently involved in some JavaScript work, for which we used JSpec to run automated unit tests within a continuous build. We ran into a few problems along the way, particularly around integrating JSpec into the build, so I wanted to walk through what we did and provide a working example that could hopefully be reused for future projects and save some of the pain.

Note: it seems that JSpec is no longer being developed and they are pointing us towards Jasmine. However, JSpec still works well as a simple tool. The tests can easily be integrated into a continuous build and don’t require a browser to be installed as they are using the Rhino engine.

Step 1: Set up the project and example test

Download the jspec files

I’m going to run JSpec using Rhino instead of a browser, which requires js.jar (included in the support directory downloaded with jspec) and env.rhino.1.2.js, which can be downloaded from Envjs.

To run jspec itself in this example, I only need the following files (from the lib directory in the jspec download):

  • jspec.js
  • jspec.jquery.js
  • jspec.xhr.js
  • jquery.js (inside spec/support directory)

For the purpose of the example, I’ve created a basic JS file that gets passed a part of a DOM and sets the HTML to be “Hello World”. The test checks that the HTML is as expected.

JSpec Test

describe('Hello', function() {

    it("writes Hello World into the dom it is passed", function() {

        var dom = $("<div></div>");

        var hello = new Hello(dom);

        dom.html().should.be("Hello World");

    });

});

Hello Class to pass the test

function Hello(dom) {
    var self = this;
    self.dom = dom;

    function setUp() {
        dom.html("Hello World");
    }

    setUp();
}

More details of the format for JSpec tests.

2. Create a script to run the tests

I’m going to create one javascript file to load and run all of the tests, which will do the following:

  1. Load the files needed to run jspec
  2. Load the source javascript files
  3. Run the tests

Script to run tests: v1

load('test/lib/env.rhino.1.2.js',
        'test/lib/jspec/jspec.js',
        'test/lib/jspec/jspec.xhr.js',
        'test/lib/jquery.js',
        'test/lib/jspec/jspec.jquery.js',
        'test/lib/formatters.js');

load('src/hello.js');

JSpec.exec("test/tests/hello.spec.js");

JSpec
        .run({reporter: JSpec.reporters.Terminal })
        .report()

I can run this from within terminal with the java –jar command1:

Java –jar lib/jar.js –opt -1 test/lib/run.js

3. Adjust the script to automatically run all tests

This script works fine if there’s only one javascript file and one test file but that’s not realistic. I can create a simple fix to:

  • Load all js files from the source directory
  • Load all js files from the tests directory

The code to load the files requires the Java class java.io.File to be imported.

Loadfiles.js

importClass(java.io.File);

function isJavaScriptFile(fileName) {
    fileExtension = fileName.substring(fileName.lastIndexOf('.'));
    return (fileExtension == ".js");
}

function getFiles(directoryPath) {
    var dir = new File(directoryPath);
    return dir.listFiles();
}

Updated Test Runner Script – v2

load('test/lib/env.rhino.1.2.js',
        'test/lib/jspec/jspec.js',
        'test/lib/jspec/jspec.xhr.js',
        'test/lib/jquery.js',
        'test/lib/jspec/jspec.jquery.js',
        'test/lib/formatters.js',
        'test/lib/loadFiles.js');

var srcFiles = getFiles("src");
jQuery.each(srcFiles, function(index, model) {
    load(model);
});

function runTestSuite(testSuite) {
    JSpec.exec(testSuite);

};

var tests = getFiles("test/tests");
jQuery.each(tests, function(index, test) {
    if (isJavaScriptFile(test.getName())) {
        runTestSuite(test);
    }
});

JSpec
        .run({reporter: JSpec.reporters.Terminal })
        .report()

4. Make the source and tests folders configurable

If you want to be able to configure the location from outside of the script itself, this is how you can do it. This means that the locations can be passed in from the build script (as in the example code), or through your project configuration.

This is really straightforward, we can just use the arguments[] array:

var testDirectoryPath = arguments[0];
var srcDirectoryPath = arguments[1];

When running from terminal, the command is now:

Java –jar lib/jar.js –opt -1 test/lib/run.js test/tests test/src

5. Create an Ant build file

The build script uses Ant to execute the tests. The task to execute the tests is a java task, which runs the rhino js.jar. Each of the above arguments is passed as a separate value. Setting failonerror=”true” ensures that if the tests fail, the build fails.

<?xml version="1.0"?>
<project basedir="." name="JSpecRunner" default="run">

    <target name="run">
        <sequential>
            <java jar="lib/js.jar" failonerror="true" fork="true">
                <arg value="-opt"/>
                <arg value="-1"/>
                <arg value="test/lib/run.js"/>
                <arg value="test/lib/jspec/"/>
                <arg value="src"/>
            </java>
        </sequential>
    </target>

</project>

6. Lastly: format the output into XML

The script so far just outputs the results to the terminal, but they can also be formatted and written to an XML file so that the output is stored. This may be useful if you are running the build script from within a continuous build server like Go or CruiseControl.

For the XML formatting, I can use an open source JSpec module. In the second line of code, ‘formatters’ should be changed to read ‘reporters’.

The formatting code needs a couple of tweaks to write a file, since the last two lines of code currently print the output:

print('<?xml version="1.0" encoding="utf-8"?>n');
print(obj2xml(out));

To write a file, firstly I need to specify the file name – I can do this in the arguments passed to the script:

var resultsFile = arguments[2];

Secondly, I can create a new file using Java functions and write the XML created by the formatting script to it:

var results = new File(resultsFile);
var file_writer = new FileWriter(results);
file_writer.write("<?xml version="1.0" encoding="utf-8"?>n");
file_writer.write(obj2xml(out));
file_writer.close();

Note: This also requires the Java.IO.File and Java.IO.FileWriter classes to be imported.

The complete script outputs the following to the terminal:

Output from terminal after running jspec tests

The XML file contains the following:

<?xml version="1.0" encoding="utf-8"?>
<testsuites >
	<testsuite name="Hello" tests="1" assertions="1" failures="0" >
		<testcase name="writes Hello World into the dom it 
			is passed" assertions="1" />
		</testsuite>
</testsuites>

Complete!

The entire code can be downloaded from Github:
https://github.com/jocranford/jspecbuild

Footnotes

1. Further links for more explanation of the arguments passed to Rhino, and specifically the optimisation options.