Developer guide: writing QUnit tests - ManvilleFog/buttonmen GitHub Wiki

Tips for writing JavaScript unit tests using QUnit

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.

How many tests to write and what to name them

  • 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".

Structure of each test

  • 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();
  });
});

DOM inspection hints

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.
⚠️ **GitHub.com Fallback** ⚠️