One troublesome question Node.js (and by extension, nodeunit) encounters is how does it know when execution of a test suite is complete? This is an example of the classic computer
393 Adjust SPA modules for tests
science halting problem, and isn’t trivial in any event-driven language. In general, Node.js considers an application complete when it can find no code to execute and it has no pending transactions.
Up to this point, our code has been designed for continuous use without consid- eration for an exit condition outside of closing the browser tab. When a tester uses mode 2 (testing in a browser using fake data) and signs out, our Fake module starts a setTimeout in anticipation of another sign-in.
Our test suite, like some film genres, requires an explicit ending. Therefore, if we intend to ever see our test suite complete this side of a SIGTERM or SIGKILL, we need to use a test setting.6 A test setting is a configuration or directive required for testing, but not required for “production” use.
As you might gather, we’d rather minimize test settings so we can prevent them from introducing their own bugs. Sometimes, they’re unavoidable. In this case we need a test setting to stop our Fake module from constantly respawning timers. This will allow our suite to exit so we can use scripts to automate the run of the test suite and interpret the results.
We can perform the following steps to prevent Fake from restarting timers after sign-out:
■ In the test suite, add a true argument to the sign-out call like so:
spa.model.people(true). This directive (which we call the do_not_reset flag) informs the Model that after a sign-out, we don’t want it to reset values in prep- aration for another sign-in.
■ In the Model’s spa.model.people.logout method, accept an optional do_not _reset argument. Pass this value as the single argument to the chat._leave method.
■ In the Model’s spa.model.chat._leave method, accept an optional do_not _reset argument. Pass this value as the data when sending the leavechat mes- sage to the back end.
■ Change Fake (webapp/public/js/spa.fake.js) to ensure the leavechat callback treats the received data as a do_not_reset flag. When the leavechat callback sees that the data it received has the value of true, it should not restart timers after sign-out.
Though that’s more work than we’d hoped (we were looking for no additional work), this only requires minor surgery on three files. Let’s start with the test suite and add the do_not_reset directive to our logout method call as shown in listing B.13. The one-word addition is shown in bold:
6 Let’s be clear—we need this program to exit because our automated commit hook will rely on analysis of the exit code. No exit means no exit code, which means no automation, which of course is unacceptable.
...
// Begin /logoutAsFred/
logoutAsFred = function( test ) { test.expect( 0 );
// logout as fred
spa.model.people.logout( true );
// proceed to next test when
// + logout is complete (spa-logout event) $deferLogout.done( test.done );
};
// End /logoutAsFred/
...
Now let’s add the do_not_reset argument in the Model as shown in the following list- ing. Changes are shown in bold:
...
people = (function () { ...
logout = function ( do_not_reset ) { var user = stateMap.user;
chat._leave( do_not_reset );
stateMap.user = stateMap.anon_user;
clearPeopleDb();
$.gevent.publish( 'spa-logout', [ user ] );
};
...
}());
...
chat = (function () { ...
_leave_chat = function ( do_not_reset ) {
var sio = isFakeData ? spa.fake.mockSio : spa.data.getSio();
chatee = null;
stateMap.is_connected = false;
if ( sio ) { sio.emit( 'leavechat', do_not_reset ); } };
...
}());
...
Finally, let’s update the Fake module to consider the do_not_reset directive when sending a leavechat message. Changes are shown in bold:
...
mockSio = (function () {
Listing B.13 Add do_not_reset to suite—webapp/public/nodeunit_suite.js
Listing B.14 Add do_not_reset to the Model—webapp/public/js/spa.model.js
Listing B.15 Add do_not_reset to Fake—webapp/public/js/spa.fake.js
395 Summary
...
emit_sio = function ( msg_type, data ) { ...
if ( msg_type === 'leavechat' ) { // reset login status
delete callback_map.listchange;
delete callback_map.updatechat;
if ( listchange_idto ) {
clearTimeout( listchange_idto );
listchange_idto = undefined;
}
if ( ! data ) { send_listchange(); } }
...
After the updates, we can run nodeunit nodeunit_suite.js and watch the test suit run and exit:
$ nodeunite nodeunit_suite.js
✔ testInitialState
✔ loginAsFred
✔ testUserAndPeople
✔ testWilmaMsg
✔ sendPebblesMsg
✔ testMsgToPebbles
✔ testPebblesResponse
✔ updatePebblesAvtr
✔ testPebblesAvtr
✔ logoutAsFred
✔ testLogoutState
OK: 25 assertions (14234ms)
$
The exit code of the suite will be the number of failed assertions. Therefore if all the tests pass, the exit code will be 0 (we can inspect the exit code on Linux and Mac using echo$?). A script can use this exit status (and other output) to do things like block the deployment of a build, or send an email to a concerned developer or proj- ect manager.