jqMock User Guide


Overview

jqMock is a lightweight javascript Mock Framework for the jqUnit testing framework. jqMock allows functions in dependent functions such as native alert dialogs to be mocked, so that your own code can be tested in isolation.

jqMock has comprehensive expectation support, with complex argument matching criteria, ordered/unordered expectations, multiplicity control, intercepting return values and throwing exceptions. Error messages are clear and explicit, and failures are presented with full details of the satisfaction state.

Getting Started

It is assumed you are already familiar with the jqUnit testing framework.

Download the source for jqMock here:

To include in your tests, add the script to your fixture:

<script type="text/javascript" src="jqMock.js"></script>

It is also suggested you make a call to jqMock.addShortcut() which puts is and expect into the global scope for cleaner code:

jqMock.addShortcut();

The Basics

assert that

jqMock provides an assertion function assertThat which allows you to setup powerful and flexible expectations by providing a comprehensive set of expressions. Even if you do not use this framework for mocking, assertThat is a very useful addition to the jqUnit toolset. For example:

jqMock.assertThat(3,3);
jqMock.assertThat([1,2,3],[1,2,3]);	
jqMock.assertThat({a:'a',b:'b'}, {b:'b', a:'a'});
jqMock.assertThat([1,2,3], is.instanceOf(Array));
jqMock.assertThat("foo", is.anyOf(['foo','bar']));
jqMock.assertThat(myobject, is.allOf([is.instanceOf(MyObject),{x:'x'}]));

Expressions

jqMock comes with the following expressions:

ExpressionMeaningExample
is.anythingmatches anything
            jqMock.assertThat(foo, is.anything);        
        
is.not()negate an expression
            jqMock.assertThat(foo, is.not(100));        
        
is.instanceOf()applies the instanceof operator
            jqMock.assertThat([1,2,3], is.instanceOf(Array));        
        
is.regex()whether it matches the regex expression provided
            jqMock.assertThat("987", is.regex(/[0-9]*/));       
        
is.anyOf([])matches one of the expressions in the array provided
            jqMock.assertThat(foo, is.anyOf(["a", "b", "c"]));       
        
is.allOf([]) must match all of the expressions in the array provided. Useful for matching multiple not expressions
            jqMock.assertThat(foo, is.allOf([is.not(null), is.not(undefined)]));       
        
is.objectThatIncludes()matches if the actual object has every field specified in the expected object
            jqMock.assertThat({x:'x', a:'a', y:'y'}, is.objectThatIncludes({a:'a'}));
        
is.custom(Function) specify a custom expression as a function that takes in a single argument and returns a boolean value
        function isEven(arg) {
	        return arg % 2 == 0 ;
        };
        jqMock.assertThat(foo, is.custom(isEven));
        
is.exception() An expression for matching exceptions, to be used with expectThatExceptionThrown(). See the section below for the different ways to create this expression.
		jqMock.expectThatExceptionThrown(function() {
			throw new Error("something bad happened");
		}, is.exception("something bad happened"));	
        

Asserting Exceptions

jqMock also provides a method expectThatExceptionThrown to assert that an exception is thrown in a block of code. The following examples shows how loose or strict this assertion can be.

	// simple compare
	jqMock.expectThatExceptionThrown(function() {
		throw "blah";
	}, "blah");
	
	// using an expression
	jqMock.expectThatExceptionThrown(function() {
		throw new Error("booya");
	}, is.instanceOf(Error));
	
	/* there is a special expression for exceptions */
	
	// any exception is thrown
	jqMock.expectThatExceptionThrown(function() {
		throw new Error("anything");
	}, is.exception());	
	
	// using a string argument will assert ex.message
	jqMock.expectThatExceptionThrown(function() {
		throw new Error("bad exception 2");
	}, is.exception("bad exception 2"));	
	
	// you can pass in an object with the attributes name, message and type, 
	// which asserts ex.name, ex.message, and (ex instanceof type) respectively.
	// you can pass in just one or all three attributes.
	jqMock.expectThatExceptionThrown(function() {
		eval("blah");
	}, is.exception({name: "ReferenceError", message: "blah is not defined", type: ReferenceError}));	

Basic Mocking

Let's start with a basic hello world example of how this mock library is useful. Say you wrote this function:

function hello() {
    alert("hello world!");
}

You can mock and intercept the call to window.alert, and setup an expectation it is called correctly:

jqUnit.test("hello world test", function() {
    var alertMock = new jqMock.Mock(window, "alert");
    alertMock.modify().args("hello world!").returnValue();
    hello();
    alertMock.verify();
    alertMock.restore();
});    

Lets analyse the above code, step by step.

Modifying the Arguments of the expectation

The args() method will setup the arguments of the expectation. These are basically the arguments you expect your method to be called with. Arguments will match the expectation if they are deeply equal. You can use the expressions seen earlier. You can provide any number of arguments. Here are some examples:

var mock = new jqMock.Mock(myobj, "fn");

mock.modify().args();
mock.modify().args([4,5,6]);
mock.modify().args( {a:'a',b:'b'}, 'c' );
mock.modify().args(is.regex(/^abc[0-9]*xyz$/));
mock.modify().args(is.anyOf(["a", "b", "c"]));
	
myobj.fn();
myobj.fn( [4,5,6] );
myobj.fn( {a:'a',b:'b'}, 'c' );
myobj.fn( "abc123xyz" )
myobj.fn("a");

mock.verify();

Modifying the Expectation Multiplicity

The multiplicity indicates how many times you expect a function to be called with some argument. When no multiplicitiy is specified as in the above examples, it defaults to a multiplicity of exactly one. These are the different ways in which multiplicity can be specified:

MultiplicityMeaningExample
mulitplicity(n)An alias for exactly(n)
            mock.modify().args().multiplicity(2);
        
exactly(n)Expect that an argument is matched exactly n times
            mock.modify().args().multiplicity(expect.exactly(1));
        
times(n)An alias for exactly(n)
            mock.modify().args().multiplicity(expect.times(3));
        
times(n,m)range multiplicity. Expect that an argument is matched at least n times, and at most m times. In the example here, the expectation is satisfied if the method is called with no arguments 1,2 or 3 times.
            mock.modify().args().multiplicity(expect.times(1,3));
        
atLeast(n)Expect that an argument is matched at least n times.
            mock.modify().args().multiplicity(expect.atLeast(1));
        
atMost(n)Expect that an argument is matched at most n times. This argument is initially satisfied, and becomes unsatisfied if it matches more than n times.
            mock.modify().args().multiplicity(expect.atMost(4));	
        

Reading the Failure Report

The failure report of jqMock uses the same format as the java mock library RMock, and provides a clear description of the failure. Consider the following test. We expect "Bart" to be called twice, "Lisa" once, and "Marge" at least once.

jqUnit.test("failing test", function() {    
    var mock = new jqMock.Mock(myObj, "sayHello");
    mock.modify().args("Bart").multiplicity(2);    
    mock.modify().args("Lisa");
    mock.modify().args("Marge").multiplicity(expect.atLeast(1));    
    doWork();    
    mock.verify();
});

In the test above, line 6 calls this code under test:

function MyObject() {};
MyObject.prototype.sayHello = function(person) {
    return "hello " + person;
};
var myObj = new MyObject();
function doWork() {
    myObj.sayHello("Bart");
	myObj.sayHello("Marge");
}

"Bart" has only been called once, and "Lisa" hasn't been called. The results will look like this:



The following diagram shows how to read the report.


Return Value

When returnValue() is not specified, the function remains intact and you are merely checking expectations. However, you will usually want to intercept and change the return value for easy testing. Calling restore will remove all expectations, and stop intercepting the function.

jqUnit.test("returnValue and intercepting", function() {
    var mock = new jqMock.Mock(myObj, "sayHello");
    mock.modify().args("Bart");                       // use original call
    mock.modify().args("Homer").returnValue("doh!");  // intercept returnValue

    myObj.sayHello("Bart");   // returns "hello Bart"   
	myObj.sayHello("Homer");  // returns "doh!"
    mock.verify();
    
    mock.restore();    
    myObj.sayHello("Homer");  // after restore, returns "hello Homer"
});

Throwing Exception

You can intercept a function and specify something to be thrown as an exception when that function is called.

jqUnit.test("throw exception", function() {
    var mock = new jqMock.Mock(myObj, "sayHello");	
	var myError = new Error("stupid Flanders");
	mock.modify().args("Flanders").throwException(myError);	
});

Advanced Concepts

Verifying Unexpected Invocations

Most mock libraries offer the ability to check for unexpected invocations, which fails a test in the event that a method is called with arguments that have not been setup as expectations. However, this is usually too strict, and results in overspecified tests which are hard to maintain.

By default, jqMock.verify() will only check any expectations you have setup. In the following example, the call on "Bart" will be ignored, while the extra call on "Homer" will show up as an error.

jqUnit.test("verify()", function() {  
    var mock = new jqMock.Mock(myObj, "sayHello");  
    mock.modify().args("Homer").multiplicity(1);
 
    myObj.sayHello("Bart");  // this is ignored
    myObj.sayHello("Homer"); // this will match the expectation
	myObj.sayHello("Homer"); // this will fail
	
    mock.verify();
}); 

The failure report would look like this:


However, if you want to also check for unexpected invocations, use the method jqMock.verifyAll().

If the example code above was changed so that the last line was verifyAll(), then the call with "Bart" will also fail.

jqUnit.test("verifyAll()", function() {  
    var mock = new jqMock.Mock(myObj, "sayHello");  
    mock.modify().args("Homer").multiplicity(1);
 
    myObj.sayHello("Bart");  // this call will also fail with verifyAll
    myObj.sayHello("Homer");
	myObj.sayHello("Homer");
	
    mock.verifyAll();  // change to verifyAll
}); 

The resulting failure report would look like this:


Ordered and Unordered Expectations

So far, we have only used undordered expectations. This means expectations can be fullfilled in any order. To use strict ordering, call setOrdered(true) on the mock before setting up any expectations

jqUnit.test("ordered expectations", function() {
	var myobj = {echo:function(s){return s;}};
	var mock = new jqMock.Mock(myobj, "echo");
	mock.setOrdered(true); // use ordered expectations
	
	mock.modify().args(1);
	mock.modify().args(2);
	mock.modify().args(3);

	myobj.echo(1);
	myobj.echo(3);
	myobj.echo(2);
	mock.verifyAll();		
});

The results will look like the following. The call to echo(1) was satisfied. However, echo(2) was not the next call, so it stops matching the expectations.


When you don't want to use the shortcuts

It was suggested that you call addShortcut() so that the expression operator is and the multiplicity operator expect are copied to the global scope. If these conflict with your code, you can just use the original namespaced objects instead:

jqUnit.test("using full namespaced objects", function() {    
    var mock = new jqMock.Mock(myObj, "sayHello");
    mock.modify().args(jqMock.is.anyOf(["Lisa","Bart","Maggie"]));
    mock.modify().args("Homer").multiplicity(jqMock.expect.atLeast(1));    
    doWork();    
    mock.verify();
});

Expectation Resolution

The following section will discuss some scenarios to explain how more complex expectations are resolved.

In unordered expectations, matching occurs from top down, starting from the first exepctation you setup to the last. For certain types of multiplicity, they will stop matching when it hits upper limit. The following multiplicity will stop matching when the upper limit is reached, so that other expectations can pick it up:

Consider the following test, and the result.

jqUnit.test("upper limit", function() {    
    var mock = new jqMock.Mock(myObj, "sayHello");
    mock.modify().args("Homer").multiplicity(1);  // match once
	mock.modify().args("Homer").multiplicity(2);  // match twice
    // function is executed 4 times
    myObj.sayHello("Homer");    
    myObj.sayHello("Homer");
    myObj.sayHello("Homer");
    myObj.sayHello("Homer");
    mock.verify();
});

You can see that the first expectation was matched once, the next expectation was matched twice, and the test failed because there was a call which was unexpected.

The atLeast(n) multiplicity behaves differently, and will ALWAYS match in order to keep the expectation satisfied. Consider this test, and the result.

jqUnit.test("atLeast", function() {    
    var mock = new jqMock.Mock(myObj, "sayHello");
    mock.modify().args("Homer").multiplicity(expect.atLeast(2));    
    mock.modify().args("Homer").multiplicity(2);    
    // function is executed 4 times
    myObj.sayHello("Homer");    
    myObj.sayHello("Homer");
    myObj.sayHello("Homer");
    myObj.sayHello("Homer");
    mock.verify();
});

You can see first expectation matched 4 times, while the second expectation did not match at all. If this situation happens to you, the solution is to switch lines 3 and 4, so that the atLeast() expectation doesn't steal all the matches. After the first expectation matches twice, it is disabled so that the second expectation is free to be used.

    mock.modify().args("Homer").multiplicity(2);
    mock.modify().args("Homer").multiplicity(expect.atLeast(2));