Unit Testing
FuncUnit
What is FuncUnit?
FuncUnit is QUnit (+)
QUnit is jQuery's unit testing tool. Like JSUnit. Like JUnit.
FuncUnit uses QUnit for organizing tests and assertions. FuncUnit extends QUnit so that you can:
What makes up a test?
FuncUnit provides the basic structure needed to
write unit or functional tests.
Modules
Tests
Assertions
Tests
Tests are the individual building blocks of
your unit tests and are the "thing" you are testing.
test("No cookie, should land on login screen (ui only)", function() {
expect(2);
ok(S("input[name='user']"), "User field exists");
ok(S("input[name='password']"), "Password field exists");
});
Modules
Modules are groups of tests with
setup and teardown methods that run for each test.
module("Contacts", {
// runs before each test
setup: function(){
// setup code
},
// runs after each test
teardown: function(){
// cleanup code
}
})
Assertions
Assertions are the essential element of any unit test.
The test author expresses the results expected (assertions) and FuncUnit compares the assertions to the actual values that an implementation produces.
test("counter", function() {
ok(Conctacts.first().name, "there is a name property");
equal(Contacts.counter(), 5, "there are 5 contacts");
});
Built-in Assertions
FuncUnit has several built-in assertions that you will use.
Here are a few of the more common ones you'll use but, there are others.
equal();
expect();
notEqual();
ok();
strictEqual()
Setting up your test
Sometimes you'll want to open a specific HTML page to test your code and other times you can use the default FuncUnit space to test your module. This is ESPECIALLY helpful when you need to test against a specific FIXTURE arrangement.
module("autosuggest",{
setup: function() {
S.open('autosuggest.html')
}
});
"S" is a copy of the jQuery "$" method. It is used to find elements in the page you're testing. But, it works slightly differently than $.
The following uses S.open(URL) to open a file before every test.
The "S Method"
The S method accepts any valid jQuery selector string, just like $.
One difference from $ is that this query will happen in the context of the window of the page you're testing, not the FuncUnit page where it runs.
Depending on the context in which S is called, this selector may be used immediately and return a jQuery collection,
or
it may cache the selector, which will be used in the asynchronous queue later to find elements when previous queued methods have finished.
Why "S"?
S is a "copy" of $, created using jQuery.sub.
All the jQuery methods that FuncUnit doesn't
overload are callable on S collections.
The reason for this is to preserve jQuery in the test page, unmodified.
If you want to use jQuery, none of its
methods are modified.
jQuery can be used to do unit testing, or to directly access the test page and do custom things.
Keeping Tests Atomic
Split up your testes granular enough such that you don't affect subsequent tests with the results from previous tests.
Your tests should typically assume "start state" at the beginning of each test and set it up into the scenario necessary to isolate unit you're trying to test.
But, once you've split up all of your tests to keep them atomic and free of side effects, how do you keep them logically organized and be able to run a specific group of tests on their own?
Modules
Modules are groups of tests with setup and teardown methods that run for each test.
All tests that occur after a call to module("Module Name") will be grouped into that module. The test names will all be preceded by the module name in the test results. You can then use that module name to select tests to run.
For example...
steal(
"funcunit",
"wsawui/controllers/login/login.js",
function(S, Login) {
module("Login", {
setup: function() {
$("#qunit-test-area").append("<div id='content'></div>");
}
});
test("login view", function() {
Login.index();
ok(S("button#login"), "Login button exists");
ok(S("button#resetPwd"), "Change Password button exists");
});
test("reset1 view", function() {
Login.reset();
ok(S("button#continue"), "Continue button exists");
});
test("reset2 view", function() {
Login.reset2();
ok(S("button#close"), "Close button exists");
});
test("password change view", function() {
Login.changePwd();
ok(S("button#changePwd"), "Change Password button exists");
});
});
module("Monitor", {
setup: function() {
$("#qunit-test-area").append("<div id='content'></div>");
}
});
test("Monitor Dashboard Journey Map", function() {
expect(3);
monitor.dashboard({
subaction: "journeyMap"
});
ok(S("#monitor-journey-view").visible, "Journey View exists");
ok(S("#monitor-map-view").visible, "Map mode visible");
ok(S(".module-actions #monitor-mode .list-icon").visible, "List icon visible");
});
test("Monitor Dashboard Journey List", function() {
expect(4);
monitor.dashboard({
subaction: "journeyList"
});
ok(S("#monitor-journey-view").visible, "Journey View exists");
ok(S("#monitor-list-view").visible, "List mode exists");
ok(S(".module-actions #monitor-mode").visible, "Map icon visible");
ok(S(".module-actions #monitor-mode .list-icon").missing, "List icon not visible");
});
But what about Fixtures?
Fixtures are Freeing
Fixtures give you the ability to not depend upon REAL data in REAL test levels with REAL backend to complete your UI work.
Fixtures but you in Control
The fact is...
creating stand-alone automated unit tests is time-consuming & seemingly impossible.
Fixtures for Manual UT
Being able to get the results you need in some obscure data scenario when the data doesn't really exist is key.
FixtureManager
"testData" vs "realData"
What's the difference?
FixtureManager: testData
testData is a URL parameter. It is an array of fixture actions separated by commas.
testData=hasRouteGeofences,routeTriggerRouteAtProcess,success1
Creating Fixtures
-
Every REST endpoint MUST have a json Fixture response file in models/fixtures/responses
-
The name of the files must be one of the following:
-
The endpoint name (e.g., session.json)
-
The action name (as defined in CXSsettings.js)
(e.g., member.orgList.json
-
NOTE: The application will bomb if the Fixture response file is NOT in place & the interface is defined in CXSsettings.js (& have fixtures turned on).
FixtureManager: testData
fixture "actions" are either:
1. The REST endpoint name (e.g., session)
2. Whatever name you gave it for clarity or necessity in the entry for the endpoint in CXSsettings.js (e.g., member.orgList)
These "actions" must have a corresponding response file by the action name + extension json in the modules/fixtures/responses directory.
Unit Testing
By stephanieshusband
Unit Testing
- 466