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.
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');
}
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'
}
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]);
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 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():
}
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
);
}
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).
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.
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.