Mocking

Mocking is a feature when you can replace some part of an object functionality with a dummy method with a predefined behaviour. There might be lots of cases when you may use mocks, but the general purpose is to isolate the code which you test and put it in known conditions.

Generally there are two methods which presents the mocking feature mock(object, method_name, mock) and undoMock(object, method_name). The first one replaces the object's method with the given name by the given mock-function. The second one brings back the original method.


Simple Example

Say we have got an element with a method which will hide the element smoothly by calling an effect library. The problem here is that the action will take time but we need to test it in the run-time. So we mock up the effect library method so it display the element immediately.

// this should be somewhere in your code
var some_element = $('some-element');
some_element.hideSmoothly = function() {
Effect.Fade(this, {duration: 0.4});
};
...
testAnObjectMocking: function() {
var some_element = $('some_element');

this.mock(Effect, 'Fade', function() {
some_element.style.display = 'none';
});

this.assert_visible(some_element);
some_element.hideSmoothly();
this.assert_hidden(some_element);

this.undoMock(Effect, 'Fade');
}


Mocks priority

Genereally you can mockup an object prototype methods and the prototype instances methods. But if you have mocked up a prototype method and mocked up the same method at the prototype instance, the instance mock will have a higher priority and will work for this one.

var AClass = function() {};
AClass.prototype = {
sayHello: function() {
alert('hello');
}
};
......
testAClassSayHello: function() {
var obj1 = new AClass();
var obj2 = new AClass();

this.mock(obj1, 'sayHello', function() {
alert('hi there');
});
this.mock(AClass.prototype, 'sayHello', function() {
alert('mocked hello');
});

var obj3 = new AClass();

obj1.sayHello(); // <- will say 'hi there'
obj2.sayHello(); // <- will say 'mocked hello'
obj3.sayHello(); // <- will say 'mocked hello'
}


Mocking wrapper

There's another handy mocking method, which called a wrapped mocking. The method applies arguments to eval a mocking sequence and additionally it gets a function which should be executed. It does the mocking, then executes the callback function and then undoest the made mock.

The interface is following.
withMock(object, method_name, mock, callback[, scope_object]);

Example

test_mocked_function: function() {
var obj = {
result: function() {
return 'Something terrible';
}
};

this.with_mock(obj, 'result', function() { return 'acceptable'; },

// inside the function the object will use the mocked method
function() {
this.assert_equal('acceptable', obj.result());
},

this // <- optional scope element
);

// outside the call the object will use the native method
this.assert_equal('Something terrible', obj.result());
}


Ajax-Mocking

Ajax-Mocking is the feature of TestCase which allows you to make xhr requests with a predefined response. There are again two main methods mockAjax(params) and undoAjaxMock(). The first one makes all the xhr requests happen immediately and return a predefined by the params response, and the second one brings back the original functionality. The params which you can pass to the mockAjax method are following

{
text: 'some text you like', // '' by default
xml: your_xml_object, // null by default
status: 200, // 200 by default
headers: {'Content-type': 'text/xml'} // {} by default
}

An average ajax-test might look like that.

var ajaxed_update = function() {
new Ajax.Updater('some-block', '/some/url');
};
....
test_ajax_load: function() {
this.mockAjax({text: '<p>some text</p>'});

ajaxed_update();

// the block will be updated right now
this.assertEqual(
'<p>some text</p>', $('some-block').innerHTML
);

this.undoAjaxMock():
}

Wrapped method

The ajax-mocking module has a wrapping method just as the usual mocking functionality. withAjaxMock(params, callback[, scope])

test_ajax_load: function() {
this.withAjaxMock({ status: 404, text: 'not found' },
function() {
// all the ajax calls here will
// return status 404 and test 'not found'.
},
this
);
}

Important Note

If you use Prototype or MooTools and use the framework level ajax classes, forget about the note, just be happy. But if you use another framework or make xhr requests manually you need to remember some points.

Because the xhr requests after mocking will get happened synchronically with the tests running you need to keep everything right and fully setup your requests before you call the send() method.

The feature won't work with Konqueror. It just doesn't allow such tricks. (But it works fine when you use Prototype or MooTools classes).


Effects Mocking

This last part is for the Prototype and MooTools users. Those frameworks have got visual effects libraries. The problem is simple and we have discussed it above. Effects take time but sometime you need test what happened after the effect work. Therefore you need to mock up effects so they got happened immediately.

Sure you can do it manually, but as we are a nice javascript testing library we have got some nice shortcuts for the purpose which will do all the stuff automatically. So we have two main methods mockEffects() and undoEffectsMock(). The first one makes all the visual effects happened immediately and the second one brings back the original functionality.

Example

test_menu: function() {
this.mockEffects();

this.assertHidden('menu-element');

Effect.BlindDown('menu-element');

this.assertVisible('menu-element');

this.undoEffectsMock();
}

And additionally we have a wrapped method for the effects mocking

test_menu: function() {
this.withEffectsMock(function() {
this.assertHidden('menu-element');
Effect.BlindDown('menu-element');
this.assertVisible('menu-element');
}, this);
}

That is all about the mocking feature. Take a look at the API-Documentation there are some handy aliases defined for the mocking methods.

If you use another framework in your work (not Prototype of MooTools) and you would like that TestCase had a support of it. Write the plug-in for your framework and send us. We will include it in the library.