Recently I have described on this blog my minimalist JavaScript library . The post would not be quite complete if I did not point out that this library named minlib.js under the mild BSD license to all interested parties is available.
As always opens, the use of a library not only by the documentation, but also by the unit tests: Unit tests show how the functions are called concrete, which produce the expected results of function calls. On the unit test page these views also observed in action be. You can debug it with interest and see step by step, what do the functions.
Several years ago I decided to test JavaScript code for the small unit test framework ECMA Unit from the Kupu project. Also in this blog I wrote about already on 1.6.2007 . So far I had no reason to regret that decision. A framework for unit tests should not just be luxurious. One must only be able to write down without a lot of redundant code once the proposed self-test functions and execute it.
"test cases" are in ECMA Unit objects to group "test". They are of a central object TestCase derived which assert the various assurances as , assertEquals, assertFalse etc. included. The testing methods are those of the test object whose name begins with the prefix test begins. Before executing a test case, a setUp () method is called , if such is defined in the test object, as is the version one tearDown () method is called . A number of test cases can be registered to run in a suite with runSuite finally () is called. For logging, there is an object html test reporter Or, for output to the console, a StdoutReporter . That is all. The (Spartan) issue of the HTML report looks as follows from:
an error occurs, you also get the hint as to which test in which test case which Assertion violated:
During development of JavaScript code you can pass on a browser window, once established ECMA-unit-test page is opened and after any change means refreshing to see that the recent tests are still running all successful.
When writing test code, it is important to the test as short and to formulate readable as possible. A comment is often better placed as a message string for an assertion. This increases the chance that the text is read in case of failure by a developer really.
Here is a simple example, which tried to test the function byId () on an element of the test page:
this.testById = function () {var element =
byId ("form2"); this.assert
( element,
"element of ID 'form2' found must");
this.assertEquals (element.id, "form2"
"element ID 'form2' must be correctly identified");}
;
assert with assurance The first checks whether the method byId () has ever returned an object. With assertEquals then follows the more specific assurances that it is the right item that was found. The division into two representations has the advantage of better readability in case of error. Would byId () namely null or undefined return, we would get without the prior assert accessing element.id an exception, such as "to element.id is null or not object " . In the above Form, however, would test the message " element ID 'form2' found must" bring what contains a specific reference to the problem.
For the test of callback functions, we can take advantage of our closures. The following method checks whether a each () iteration with the special exception $ break is really left:
/ / ---
this.testLeaveEachWithBreak = function () {var
lastItem;
[1, . 2,3,4,5,6,7] each (function () {
lastItem = this;
if (this == 5) {throw $ break;}}
);
this.assertEquals (lastItem, 5,
"each () can be aborted break with $"
);
};
The anonymous Iteratorfunktion this test can access the local variable lastItem , although not in its own scope, but in which the test method was declared. Thus, we can easily check the version of the loop, whether lastItem really has the expected value.
I had announced to the file minlib.js not want to expand significantly. The function extend () I've added is their size is certainly negligible, but not their meaning. The function is recommended by the Mozilla developer community inheritance mechanism. Using extend () can you implement inheritance in such a way as the following test redefine function presents:
this.testRedefineFunction = function () {var A
= function (x, y) {this.x =
x;
this.y = y;}
;
A.prototype
= {t: x 1,
: null,
y: null,
f: function () {return
"t:" + this. t + "x:" + this.x + ", y:" + this.y;}
};
var B = function (x, y, z) {
A.call (this, x, y);
this.z = z;}
;
B.prototype = {z: null,
f: function () {return
A.prototype.f.call (this ) + "z:" + this.z;
}};
extend (A, B);
var b = new B (2,3,4);
this.assertEquals (bf () , "t: 1, x 2, y 3, z: 4");}
; The test multiple shows at once:
- methods of an object is not defined in the constructor, but in the prototype. Thus, the declaration only run once (and not every instance of education).
- All components the object should be listed in the prototype.
- must not occur all the components already in the constructor. Here, for example, the attribute t is only a prototype of the object.
- In the constructor of B you see a call to the super constructor means A.call (this, x, y) (and not just A (x, y) ). Only means working at the desired instance, namely to extend just fine instance of B. The
- () function takes the prototype of A prototype of the B . If possible, by mere reference to the allocation of the prototype (who is like everything in JavaScript is just a hash) to the provided pseudo-attribute __proto__ . If this is not exposed by the JavaScript implementation, at least all prototype components A in B be taken that were in B not defined.
- Access to x and y would extend without (B, A) work in the subtype B , since these components are also defined in the constructor of A . Access to t but would not extend (B, A) unsuccessful because t in the prototype, but is not used in the constructor.
- Man redefined functions by being defined with the same names in the prototype of subtype. As in the constructor of the super constructor, you can in the redefined method using A.prototype.f.call (this ,...) call the super method.
How to test a function that performs HTTP requests - for example, the function of doRequest minlib.js ? The unit test only needs the code of the function doRequest check for yourself, but not the HTTP requests to be executed. The unit test checks whether the XMLHttpRequest object using the doRequest Aufrufparemeter of is instrumented correctly. For this purpose, the function getRequestor , depending on the browser obtains the correct version of XMLHttpRequest be overridden by a suitable mock-function.
In the test case for HTTP, we therefore define the following code for a cheap replacement object for XMLHttpRequest - an object that acts as if it controlled all the operations of a veritable XMLHttpRequests :
function RequestorMock () {};
RequestorMock .
prototype = {header fields: {},
readyState: 0,
onreadystatechange: null,
body: null,
open: function (action, url, sync) {
this.Url = url;
this.action = action;
this.sync = sync;}
,
send: function (data) {
this.body = data;
this.readyState = 4;
if (this.onreadystatechange) {
this.onreadystatechange.call (this);}
},
setRequestHeader: function (name, value) {
this.headerFields [name] = value;}
}; In send () method Mocks of the immediately readyState = 4 made and calls the callback function. That's enough to make the right use of the XMLHttpRequest object in doRequest to Prüfer.
function () {
this.name = "http request";
was getRequestorSave = null;
was requestorMock = null;
this.setUp = function () {
getRequestorSave = GetRequests;
GetRequests = function () {
Return requestorMock = new RequestorMock ();}
;
};
this.tearDown = function () {
GetRequests = getRequestorSave;
};
this.testDoRequest = function () {
was data = 'var i = 0; ',
url =' http://ruediger-plantiko.net ',
callbackCalled = false;
/ / doRequest (url, callback, data, action, header fields)
doRequest (url,
function () {callbackCalled = true;},
data,
"POST",
{"Content-Type: text / javascript"} );
this.assertEquals (requestorMock.url, url);
this.assertEquals (requestorMock.body, data);
this.assertEquals (requestorMock.headerFields ["Content-Type"],
"text / javascript") ;
this.assertEquals (requestorMock.action, "POST");
this.assert (callbackCalled, "the callback function must be called"
);
};} Is
in the setup so the (global) function getRequest by the pointer to an anonymous function is overridden, that returns a mock instance. We have to keep the instance to continue to use in a local private variables requestorMock to the state of the object in undertaking part of the tests. Teardown in the original function is restored.
In the test method itself is called only the doRequest () method with some dummy data. The HTTP request even though it is never executed. But it is checked that the call is correct. And that is precisely the task of the function doRequest () : The HTTP request according to the parameters passed when called to execute correctly.
The dispatch of forms is used to verify computer even easier by replacing the form action attribute that normally the destination URL, a dummy URL, for example action = "javascript: void (-1) , ". Then, after the submit () be inspected, if the form was filled as desired. How the test gotoURL () function:
this.testGotoURL = function () {var elem
, parent;
var action = "javascript: void (-1);"
gotoURL (action, {field test: "test value"});
elem = firstByName ("test field");
this.assert (gotoURL create () must form field "elem);
this.assertEquals (elem. nodeName, "INPUT");
this.assertEquals (elem.type "hidden");
this.assertEquals (elem.value, "test value");
parent = elem.parentNode;
this.assertEquals (parent. nodeName, "FORM");
this.assertEquals (parent.action, action);
this.assertEquals (parent.method, "post");}
; The proper registration and deregistration of event handlers by the functions registerFor () can and unregister () test automatically when you know how to produce a simulated event, eg a click event. In this case, once scores of Internet Explorer to trigger a mouse click on the item e , to program here e.click () - it's not easy!
fire navigator.userAgent.match click = (/ MSIE /)?
function (element) {
/ / IE = beautifully simple
element.click ();}
:
function (element) {
/ / All other: The same total cumbersome
var evObj document.createEvent = ('mouse events') ;
evObj.initEvent ('click', true, true);
element.dispatchEvent (evObj);}
;
The straight failure to test whether a registered click handler is also invoked can be written as follows then:
function () {var f = null
;
idCopiedByHandler var = "";
this.setUp = function () {
idCopiedByHandler = "";
f = registerFor (byId ("btnTest"), "click", function () {this.id =
idCopiedByHandler;
});}
;
this.tearDown = function () {
unregister (byId ("btnTest"), "click", f);}
;
/ / ---
this.testHandlerCalledOnClick = function () {var
btnTest byId = ("btnTest");
fire click (btnTest);
this.assertEquals (idCopiedByHandler, "btnTest "
" Registered click handler must be called at Click ");}
;
...
}
whether the callback is called, can be recognized that the ID of the element (can be accessed within the handler with the pseudo-variable this ) in the private variable idCopiedByHandler copied.
multiple tests with the same registration function to run, I liked to put the registration in the setUp and tearDown remove. This reduces, as we see the code of the test method dramatically to the absolute essentials: The triggering of the clicks and the verification that the click handler was called.
For JavaScript libraries unit tests of this type are already sufficient. For Web applications that work intensively with JavaScript, of course, more extensive application testing is appropriate, for example with QTP or Selenium. That would be the subject of a separate blog.
0 comments:
Post a Comment