Hemi - JavaScript Framework - Get Started with TDD

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

Get Started with Hemi and TDD

Overview

Hemi includes framework-level unit testing capabilities with the Test Module and on-demand performance testing with JavaScript Function Monitor. The included Test Suite Template and Fragment make it very easy to write in-page unit tests for existing applications, or to implement Hemi while following TDD best practices.

The goal of this Get Started with Hemi article will be to create the same objects as are created in the Get Started with Hemi and DWAC, except to do the testing up front. If you read and worked through the Get Started with Hemi and DWAC, you can reference your project files from these tests, assuming both this page and the DWAC tool are on the same domain. The objects we want to create are:

  • "Example Template Container", a template that will serve as the entry point.
  • "ExampleComponent", a component that will be referenced within the first template, and be used to load a second template.
  • "Embedded Template", a template that will include a fragment.
  • "Example Fragment", a fragment that will include some XHTML.

Introduction to the Test API and Test Tools

First, let's get familiar with the test tool, Test Suite, and the Test API. Test Suite may load tests from external .js files, or from custom text. The syntax of a test is straightforward: A JavaScript function that starts with Test, which receives one parameter, TestResult, and which may return true or false. The JavaScript is loaded through the Test Module into the Module service. The result is that the once global Javascript function becomes an intimate part of a unique Test Module instance. The test has its own context, lifecycle, and works with other tests defined in the same text block or script file to form a Test Suite.

Consider the following example test:

// A test created for 'something'.
function TestSomething(oTestResult){
   // Log results are visible from the console
   this.log("Example test");
   this.Assert(true);
}
                    

Click the 'Run Current Test' button to try it out. Tip: You can type in your own test function into the text field and click the 'Reset Current Test' button.

Apart from writing a javascript function that begins with Test, one other important feature to keep in mind is synchronicity. Every test is assumed to by synchronous, and, therefore, the return value is assumed to be true. By returning false, the Test indicates it is not finished processing. To finish processing, the Test must at some point invoke the EndTest method. Behind the scenes, the test service uses the Task Service to keep track of the test states.

The following example test demonstrates asynchronous tests.

function TestStartAsyncSomething(oTestResult){
   // Do something, waiting for a reult
   return false;
}
function TestFinishAsyncSomething(oTestResult){
   // Use this test to finish the previous test.
   EndTestStartAsyncSomething(true);
}
                    

Notice there are two tests. Click 'Run' next to the first test. Notice that the test does not complete. Click 'Run' next to the second test and notice the first test finishes.

Creating Tests for the Custom Objects

The first set of tests should be to try to load each of the non-existent objects: two templates, one fragment, and one component. The templates and components are easy because they are first-class Application Component features. In fact, the hemi.app library includes wrappers to make it pretty easy to load, the Feature Verification Tests (FVTs) include examples (template tests : test.app.comp.js), and the Runtime Container fragment used for DWAC projects provides ever more example material.

Template Tests

This test will assume that at some point a file named ExampleTemplateContainer.xml will exist in the same directory as the page running the test..

There are several ways to load templates. An XML request, a direct node reference, via the hemi.app.createApplicationComponent wrapper, or building up the necessary hemi.object.xhtml and hemi.app.comp objects, or using the template attribute within an Application Space. The easiest way to load a template is with the attribute, such as: <div rid = "ExampleTemplateContainer" template = "ExampleTemplateContainer.xml" /> This is also a hard test to reuse because it is so dependent on a clean environment, and assumes the test host already made an attempt to load the test template.

// Given some HTML on the page
// <div rid = "ExampleTemplateContainer" template = "ExampleTemplateContainer.xml" />
function TestTemplateWithSpaces(oResult){
    var oSpace = Hemi.app.space.service.getPrimarySpace();
    var oObject = oSpace.getSpaceObjectByName("ExampleTemplateContainer");
    if(oObject && oObject.object)
        this.Assert(oObject.object.getContainer().innerHTML.match(/Example Template Container/),"Example text not found");
}

Although it is more work on the testing side, adding a few extra lines makes it more reusable.

function TestFragment(){
    var oDiv = document.createElement("div");
    var oX = Hemi.object.xhtml.newInstance(oDiv, 1);
    var oA = Hemi.app.comp.newInstance(0,0,oX.getObjectId(),0,0,1);
    oA.setTemplateIsSpace(1);
    oA.setAsync(false);
    var oXml = Hemi.xml.getXml("Examples/ExampleTemplateContainer.xml");
    this.Assert(oXml != null, "Template not found");
	oA.loadTemplateFromNode(oXml.documentElement);
    this.Assert(oA.getContainer().innerHTML.match(/Example Template Container/i),"Example text not found");
}

Testing can be a bit tricky when using loadTemplate because the method only serves as an input and not an output, primarily to handle asynchronous requests. Therefore, the test would need to be asynchronous and listen for the ontemplateload message. Because the template doesn't exist, we also need an extra check to determine it failed to load. A setTimeout is used for two seconds. If the template loads within that two seconds, a property is set on the test. At the end of two seconds, the test is ended with the result being the state of that property. Whenever the template loads, the property will be true and the test will pass.

this.Initialize = function(){
    Hemi.message.service.subscribe("ontemplateload",HandleTemplateLoad);
};
this.Unload = function(){
    Hemi.message.service.unsubscribe("ontemplateload",HandleTemplateLoad);
    var oComp = Hemi.app.space.service.getPrimarySpace().getSpaceObjectByName("Test Example Template");
    if(oComp && oComp.object) oComp.object.destroy();
};
function CheckTemplateLoad(){
    EndTestLoadExampleTemplateContainer((Module.getProperties().template_loaded ? true : false));
}

function HandleTemplateLoad(sMessage, vData){
    if (vData.getContainerComponentId() == "Test Example Template")
        Module.getProperties().template_loaded = 1;
    
}
function TestLoadExampleTemplateContainer(oTestResult){
    // Make an HTML element for the Template
    var oDiv = document.createElement("div");
    oDiv.appendChild(document.createTextNode("Test"));
	document.body.appendChild(oDiv);
    this.getObjects().div = oDiv;

    // Give the component the RID: "Test Example Template"
	var oComp = Hemi.app.createApplicationComponent(0, oDiv, Hemi.app.space.service.getPrimarySpace(),"Test Example Template");
    window.setTimeout(CheckTemplateLoad,2000);
	oComp.loadTemplate("Examples/ExampleTemplateContainer.xml");
    return false;
}
                    

Component Test

Component tests are straight forward, provided the component file name follows the convention component.[name].xml.

function TestLoadComponent(){
	var oComp = Hemi.app.createApplicationComponent("Examples/component.test.xml");
	this.Assert(oComp, "Component was not created");
    this.Assert((oComp.getReadyState() == 4), "Component readyState is unexpected.");
}  
                    

Fragment Test

Fragments are bits of XHTML and script that are reusable components within the object space of a template. Fragments are largely conceptual in nature in that they exist as a space definition, import-xml, and that, like Templates, they are pre-processed by Application Components for special tokens and embedded-script statements. Application Space configuration provides the abstraction and primary processing mechanism for Fragments and Templates. In fact, Fragments ande Templates look very similar, with three notable exceptions:

  1. Templates may define both template_init and template_destroy, and embedded_init and embedded_destroy handler sets, while Fragments may only define the embedded_init and embedded_destroy handlers.
  2. Templates must have a <Template /> root node, while fragments must define a root node that matches the space definition (default is <html-fragment />)
  3. Templates can be loaded directly from an application component while Fragments must be loaded indirectly through a template or space configuration.

Much like testing Templates, there are several ways to test Fragments. The easiest way would be to define the fragment in an existing template, and then reuse the existing Template test. However, this does not make for maintainable tests. Ideally, the Fragment test would not be dependent on a Template. So, that is the unit test we will make.

function TestFragment(){
    var oDiv = document.createElement("div");
    var oX = Hemi.object.xhtml.newInstance(oDiv, 1);
    var oA = Hemi.app.comp.newInstance(0,0,oX.getObjectId(),0,0,1);
    oA.setTemplateIsSpace(1);
    oA.setAsync(false);
    var oTemplate = Hemi.xml.parseXmlDocument("<Template><import-xml src = \"Examples/ExampleFragment.xml\" /></Template>");
	oA.loadTemplateFromNode(oTemplate.documentElement);
    this.Assert(oA.getContainer().innerHTML.match(/example fragment/i),"Fragment text not found");
}