Developer guide: writing QUnit tests - ManvilleFog/buttonmen GitHub Wiki
We use QUnit to run automated unit tests for the buttonmen code. This is an incomplete list of things to keep in mind when you're writing tests.
- Write one test for each JS function, named "test_Module.function" --- the audit script will expect this, and will complain if you make a new function the source and don't make a corresponding test function. It's fine to make additional tests for a function if you'd like --- in general, you can put as many asserts as you'd like in a given test function, but if you want to test with more than one set of responses from dummy_responder, you should make a new test function. Name additional test functions "test_Module.function_reason".
- Most functions make reference to some global objects, Module.someobj, which are populated from responder/dummy_responder. So in general, you're going to need to invoke a responder function to test any JS function.
- In the rare event that you don't need global object data to test a particular function, i.e. the function just operates on its input and returns output, you can use the "test" QUnit primitive
- The most common pattern is:
- Use "asyncTest" in QUnit to setup a test which can wait for the results of a POST
- Invoke some sort of data-get function, Module.getSomeData(), from the module under test, and use your QUnit test function logic as the callback
- In your QUnit function logic, setup an empty page object, Module.page, so you'll have somewhere to attach the results of the function under test
- Next, run the function under test:
- If the function is Module.addSomething(), it should internally append to Module.page, so you don't need to do anything else
- If the function is Module.someThing(), it should return page elements as its return value, so capture those and append them to Module.page yourself
- Next, use Module.layoutPage() to actually render the Module.page object as a page. (N.B. Functions whose names start with Module.action should layout the page themselves, so this shouldn't be necessary. See DOM inspection hints below for more commentary on this.)
- Next, run whatever checks you want to run (again, see below for more hints)
- Next, use "start()" to tell QUnit to actually start the asynchronous test
Putting that all together:
asyncTest("test_Module.calculateWidget", function() {
Module.getModuleStuff(function() {
Module.page = $('<div>');
var widget = Module.calculateWidget();
Module.page.append(widget);
var item = document.getElementById('interesting_widget');
ok(item, "Should define an interesting widget element");
start();
});
});
The advice above says to always use Module.layoutPage to actually render an HTML page and inspect that. Why bother? In principle, Module.page is now a jQuery object containing the entire contents of your page, so you could test it using jQuery routines. I (Chaos) haven't yet figured out how to get consistent results from doing that, and have found that it's much easier to setup a page and use DOM inspection routines to inspect the page contents.
Here's an incomplete list of DOM inspection routines i've used for testing. Also see http://www.w3schools.com/jsref/dom_obj_all.asp:
- How to get elements:
- document.getElementById(): lookup an element by ID. You should not put a "#" at the beginning of the ID here.
- How to test elements:
- someelem.nodeName: a parameter containing the name of the type of element this is, in all-caps. So you would compare this to "DIV", "TABLE", etc
- someelem.innerHTML: a parameter containing the full HTML contents of the element, not including the element itself. You can use match() on that resulting string to do a quick-and-dirty check for whether some contents of interest exist within the element.