Chapter 51. Unit Testing

Table of Contents

1. Overview
2. Including the lzunit component
3. Writing test cases
4. Advanced techniques
4.1. Sequential execution of tests
4.2. Deferred test execution
4.3. Test setup and cleanup
5. Chaining tests

LzUnit is OpenLaszlo's implementation of the xUnit testing framework. LzUnit enables automated unit-testing of Laszlo applications and libraries.

1. Overview

The functionality provided by the LzUnit framework is essentially comprised of two public classes — TestCase and TestSuite. Each TestSuite contains one or more children that are instances of TestCase. An LZX program that consists of a <TestSuite> will, when loaded, automatically run all of its child TestCases and report the number of test cases run, the number of failures, and the number of runtime errors. If any failures occur, an obvious error message is presented.

2. Including the lzunit component

The unit testing code is not a part of the Openlaszlo Runtime Library; you must explicitly include it using <include href="lzunit"/>. The <lzunit> library has a <debug y="500"/> in it. If you would like to see the debugger elsewhere (as in the examples below), assign it a different "y" value. Put the <debug> before the include statement. (The compiler ignores all but the first occurence when it sees two debug tags.)

3. Writing test cases

An LzUnit test case is generally defined as an instance of TestCase or its subclass. LzUnit test cases contain one or more logical tests that are represented by corresponding methods on the <TestCase> . In order for a test to be recognized by and added to the enclosing <TestSuite>, its name must start with "test". Within each test, you use the standard assertXXX() methods of the xUnit API to define the checkpoints that will form the basis of your test. Below is an example of typical usage of the LzUnit framework that shows a success, a failure, and a runtime error.

Example 51.1. LzUnit example

<canvas debug="true" height="600">
  <debug y="500"/>
  <include href="lzunit"/>

  <TestSuite>
    <TestCase>
      <attribute name="prop1" value="'foo'" when="once"/>
      <text name="mytext" width="200" bgcolor="blue" text="LzUnit example"/>

      <method name="testsuccess">
        assertEquals(prop1, 'foo')
      </method>
      <method name="testfailure">
        assertTrue(mytext.multiline, "This is not a multiline text field")
      </method>
      <method name="testerror">
        callFoo()
      </method>
    </TestCase>
  </TestSuite>
</canvas>

In this example, as is the case with any tests that include visual elements, you do not see the text field because it was positioned below the test status panel by the layout which controls them.

[Note] Note

You must explicitly tell the compiler to include the LzUnit library in your code by using the <include> tag.

Also, when writing a test suite, remember to enable the debugger, as is done in the example above, in order for runtime errors to be detected. If you would like to see debugger warnings, but do not want them to affect results of the test run, you should set a global flag catchErrors to false in a <script> tag.

4. Advanced techniques

4.1. Sequential execution of tests

By default, the order of execution of individual tests within a test case is not guaranteed. That is, every test is executed at the next idle event (see the LzIdle documentation for details), and thus tests that take longer to execute may finish after those tests that were started later but do not need as much time to complete. If it is important that tests are run in the order they are written, set the global flag asynchronousTests to false, as in the following example.

Example 51.2. Sequential execution of tests

<canvas debug="true" height="400">
  <debug y="80"/>
  <include href="lzunit"/>

  <script>
    asynchronousTests = false
  </script>

  <class name="syncrun" extends="TestCase">
    <attribute name="counter" value="1" when="once"/>
    <method name="testfirst">
      assertEquals(1, counter++)
    </method>

    <method name="testsecond">
      assertEquals(2, counter++)
    </method>

    <method name="testthird">
      assertEquals(3, counter++)
    </method>
  </class>

  <TestSuite>
    <syncrun/>
  </TestSuite>
</canvas>

4.2. Deferred test execution

In many cases, you will encounter that the objects that your test case incorporates are not ready for inspection by the time it runs. Some of the common examples of this scenario are tests that include animation, loading of remote data or media, and instantiation of classes declared with deferred initialization stages. The approach illustrated by the example below involves waiting for the event that signals completion of a specific action, and putting the checkpoint in the event handler method.

Example 51.3. Deferred execution of tests

<canvas debug="true" height="800">
    <debug x="100" y="280"/>
    <include href="lzunit"/>
    
    <TestSuite>
        <TestCase>
            <view name="redbox" bgcolor="red" width="50" height="50">
                <animator name="anm" attribute="y" from="200" to="-400" duration="1000" start="false" 
                relative="false" onstop="parent.parent.checkStatus()"/>
            </view>
            <method name="testanim">
                redbox.anm.doStart()
            </method>
            <method name="checkStatus">
                assertEquals(-400, redbox.y)
            </method>
        </TestCase>
    </TestSuite>
</canvas>

4.3. Test setup and cleanup

Sometimes it is necessary to reset execution environment to its initial state before running every test in a single test case. The way to do so is to override the setUp() and, optionally, tearDown() methods of TestCase (the default implementation of these methods is a no-op). These methods are called before and after each of the test methods are invoked. Here's an example of their use.

Example 51.4. setUp and tearDown

<canvas debug="true" height="800">
 <debug y="500" />
  <include href="lzunit"/>

  <dataset name="places">
    <country name="U.S.">
      <state code="CA" capitol="Sacramento">
        <city>San Francisco</city>
        <city>Los Angeles</city>
        <city>San Diego</city>
      </state>
      <state code="NJ" capitol="Trenton">
        <city>Newark</city>
      </state>
      <state code="NY" capitol="Albany">
        <city>Buffalo</city>
        <city>New York City</city>
      </state>
    </country>
  </dataset>
    
  <TestSuite>
    <TestCase>
      <attribute name="start" value="'places:/country[1]'"/>
      <datapointer name="nav" xpath="$once{parent.start}"/>
      <method name="setUp">
        // Move pointer to first state in selected country
        nav.selectChild()
      </method>

      <method name="tearDown">
        // Reset pointer to starting position
        nav.setXPath(start)
      </method>

      <method name="testsettext">
        assertTrue(nav.selectChild())
        assertTrue(nav.selectNext())

        var oldText = nav.getNodeText()
        nav.setNodeText('Oakland')
        assertEquals('Oakland', nav.xpathQuery('text()'))
        nav.setNodeText(oldText)
        assertEquals(oldText, nav.getNodeText())
      </method>

      <method name="testBumpChild">
        assertTrue(nav.selectNext(2))
        assertEquals(2, nav.getNodeCount())
        assertTrue(nav.selectChild())
        nav.deleteNode()
        assertFalse('Buffalo', nav.getNodeText())
        assertTrue(nav.selectParent())
        assertEquals(1, nav.getNodeCount())
      </method>
    </TestCase>
  </TestSuite>
</canvas>

5. Chaining tests

Consider the case of a testSuite that has two tests for animation. The test will report that it hasn't failed *before* the test is complete. This is to be expected, since the animator's doStart() returns right away, and the test progress bar fills up as soon as the test method returns. But that does not invalidate the test results, since failures will still be clearly reported. To see that this is so, try inserting assertTrue(false) after the first assertion and see what happens.

If you are testing animation you need to start your second test from the callback of the first. Results are often different when you run animations sequentially or simultaneously.

If you declare a sequential animatorgroup around the animators, you can chain the checkpoints, without having to start the individual animators, as shown below.

Example 51.5. Chaining tests

<canvas height ="300" width="400" debug="true">
  <debug y="80"/>
 <include href="lzunit"/>
  <view>
    <view id="bluebox" bgcolor="blue" width="30" height="30">
      <animatorgroup name="AG1" attribute="x" process="sequential" start="false" relative="false">
        <animator from="0" to="100" duration="2000"
            onstop="canvas.animatorsuite.animatorcase.checkSimpleXAnim()"/>
        <animator from="0" to="200" duration="3000"
            onstop="canvas.animatorsuite.animatorcase.checkSimpleXAnim2()"/>
      </animatorgroup>
    </view>
  </view>
  
  <TestSuite name="animatorsuite">
   <TestCase name="animatorcase">
     <method name="testSimpleXAnim">
       bluebox.AG1.doStart()
     </method>
     <method name="checkSimpleXAnim">
       assertEquals(100, bluebox.x);
     </method>
     <method name="checkSimpleXAnim2">
       assertEquals(200, bluebox.x);
       Debug.write('--- test complete ---');
     </method>
   </TestCase>
  </TestSuite>
</canvas>

Of course, running both animators simultaneously will superimpose the effect of each on the other and the tests will fail; this is true of any simultaneous animators applied to the same attribute of the same object.