Hemi - JavaScript Framework - Get Started with Unit Testing

AJAX, Monitoring, Portal Sevices, Templates, HTML-Code-Behind - Since 2002

Get Started with Hemi and Unit Testing

Overview

The Hemi Test Module service, Test Suite template, Test Suite fragment, and testable Application Component provide a rich set of tools for in-page unit testing. As discussed in Get Started with Hemi and TDD, the Test Suite features provide a lot of quality assurance coverage. For the developers, unit testing Web applications may require a lot of mocking at the model and controller levels, and leave the view level virtually uncovered. In-page unit testing expands test coverage to include the rich behavior that is now a norm on most Web sites.

The Hemi approach to unit testing is twofold: First, to provide a set of tests that may be executed over an entire page (as discussed in Get Started with TDD), and Second, a component to instrument tests on a specific node. The value of node-level unit testing is that developers can target specific behaviors of an interface element, including any dressing created- or decorated-by a toolkit (E.G.: A JQuery or Dojo widget).

Adding unit test capabilities is straight forward. Creating a test suite and test is as simple as writing a function that begins with Test. Tests extend Modules, are decorated with the module API such as the Container and Module reference objects, and operate within an enclosed scope. Test implementations are Framework objects, and each invocation of a test generates a TestResult object. Tests are also decorated with the Logger utility, the TestMembers array of discovered tests, and the Assert, RunTest, RunTests, and getReport methods. For asynchronous operations, and EndTest is generated for each discovered test, so a handler defined within the test suite may complete the corresponding test.

Creating a Unit Test

The following example code represents some HTML node and a corresponding unit test. The unit test asserts that the node contains some specific text. When this test is started, a floating dialog is positioned over the target node. In this example, the autoTest property is not specified and a Run button is added by the testable component.

<!-- Example HTML -->
<p>Test Text</p>

<!-- Unit Test -->
function TestNodeContent(oTest) {
   this.Assert(this.Container != null, "Container is null");
   this.Assert(this.Container.innerHTML.match(/Example Content/), "Content not found");
}

Click Unit Test #1, and then click the Run button. The example HTML node and unit test instrumentation will be loaded into the Runtime Container. The result will be an error because the example content was not found. While the test is still displayed, click the Report button to view the results. Also, you may click the Log button to view any log entries that the test may have created.

Now, experiment with the same test and valid content.

<!-- Example HTML -->
<p>Example Content</p>

Click Unit Test #2, and then click the Run button. The result will be a full green icon because all tests (there was only the one) completed successfully. Click the Log button to see the results. Thise time, notice that the log indicates that both the test and test suite completed. In Unit Test #1, neither the test nor the suite completed because the one test was in error.

The first two examples loaded the unit test and example node into the Runtime Container for demonstration purposes. Under normal circumstances, the unit test would run automatically and against the actual node, as demonstrated in the next section.

Instrumenting the Unit Test

There are several ways to instrument Hemi unit tests with the testable component. Each has it's benefits and drawbacks.

One method is to use the component attribute to add the testable Application Component, a feature of the XHTML Object that works in conjunction with the Application Space service. The unit tests to run are specified with the tests attribute. This is fine for generated HTML, or with server-side tokenization to make the attribute inclusion conditional. However, for content that is typed against a schema or DTD, this may not be desired or permitted.

<!-- Example Instrumentation #1 -->
<!-- define the unit tests on the HTML element -->
<p component = "testable" tests = "test.content.example">Example Content</p>

Example Content

A second method is to use a JSON-esque embedded CSS construct. With this method, the automatic instrumentation is eschewed for a reduced footprint and by-passing strong schema validation. However, without the automatic instrumentation, some additional script is needed to bind the node to the testable component, and pass along the configuration. Any means to identify the nodes with the embedded CSS class may be used to aggregate a list of matching nodes. The hemi.css library includes a utility to decompose the construct into a JSON object. In the following example, the primary Application Space is used.

<!-- Example Instrumentation #2 -->
<!-- define the unit tests in an encoded format -->
<p class = "{#HF_WebUnit{tests:'test.content.example'}}>Example Content</p>
<script type = "text/javascript">
   var oObj = Hemi.css.decodeJSON(i);
   if (oObj) ApplyEmbeddedConfiguration(oObj, Hemi.app.getPrimarySpace().getSpaceObjectsByClass(i));
);
function ApplyEmbeddedConfiguration(oConfig, aObjects) {
   if (oConfig.WebUnit) {
      for (var i = 0; i < aObjects.length; i++) {
         var oComp = Hemi.app.createApplicationComponent("testable");
         voComp.getProperties().tests = oConfig.WebUnit.tests;
         oComp.getProperties().autoTest = oConfig.WebUnit.autoTest;
         oComp.getProperties().testPath = oConfig.WebUnit.testPath;
         oComp.getProperties().testTarget = (
            aObjects[i].object && aObjects[i].object.getContainer
            ?
            aObjects[i].object.getContainer() : aObjects[i]
         );
         oComp.setupTests();

      }
   }
}
</script>

Example Content

A third method is to script the application component to the node.

<!-- Example Instrumentation #3 -->
<!-- Script the unit tests -->
<script type = "text/javascript">
<p id = "oPara">Example Content</p>
function applyUnitTest(){
         var oComp = Hemi.app.createApplicationComponent("testable");
         oComp.getProperties().tests = "test.content.example";
         oComp.getProperties().autoTest = 1;
         oComp.getProperties().testPath = "Examples/Tests/";
         oComp.getProperties().testTarget = document.getElementById("oPara");
         oComp.setupTests();
      }
   }
}
</script>

Working with Multiple and Asynchronous Tests

Defining multiple tests is straightforward: Add additional tests separated by a comma.

<p component = "testable" tests = "test.content.example,test.content.example2"></p>

Handlers may be specified for receiving callbacks when a test status changes or when a test suite completes. This is helpful when working with asynchronous tests and responding to the results. By returning false and not raising an error or asserting a false condition, a test identifies itself as asynchronous. The test will remain in a pending state until it has completed.

To complete the asynchronous test, the generated EndTest function is used to specify the result of the test.

function TestAsyncAction(oTest){
   // invoke async action with a  locally defined handler.

   return false;
}
function HandleAsyncAction(){
   EndTestAsyncAction(true);
}