Basic Usage Example

This example shows basic usage of the YUI Test standalone for testing browser-based JavaScript code. Two different TestCase objects are created and added to a TestSuite object. The TestRunner is then used to run the tests once the page has loaded. The YUI 3 Console is used to output the results of the tests.

See the example in action

Creating the first TestCase

The first step is to create a new YUITest.TestCase object called dataTestCase. To do so, using the YUITest.TestCase constructor and pass in an object literal containing information about the tests to be run:

var dataTestCase = new YUITest.TestCase({

    //name of the test case - if not provided, one is auto-generated
    name : "Data Tests",

    //---------------------------------------------------------------------
    // setUp and tearDown methods - optional
    //---------------------------------------------------------------------

    /*
     * Sets up data that is needed by each test.
     */
    setUp : function () {
        this.data = {
            name: "test",
            year: 2007,
            beta: true
        };
    },

    /*
     * Cleans up everything that was created by setUp().
     */
    tearDown : function () {
        delete this.data;
    },

    //---------------------------------------------------------------------
    // Test methods - names must begin with "test"
    //---------------------------------------------------------------------

    testName : function () {
        var Assert = YUITest.Assert;

        Assert.isObject(this.data);
        Assert.isString(this.data.name);
        Assert.areEqual("test", this.data.name);
    },

    testYear : function () {
        var Assert = YUITest.Assert;

        Assert.isObject(this.data);
        Assert.isNumber(this.data.year);
        Assert.areEqual(2007, this.data.year);
    },

    testBeta : function () {
        var Assert = YUITest.Assert;

        Assert.isObject(this.data);
        Assert.isBoolean(this.data.beta);
        Assert.isTrue(this.data.beta);
    },

    //---------------------------------------------------------------------
    // Test methods - also, may just have a space in the name
    //---------------------------------------------------------------------
    
    "This test should fail": function(){
        var Assert = YUITest.Assert;
        
        Assert.fail("This test was supposed to fail.");
    }    

});

The object literal passed into the constructor contains a number of different sections. The first section contains the name property, which is used to determine which YUITest.TestCase is being executed. A name is necessary, so one is generated if it isn't specified.

Next, the setUp() and tearDown() methods are included. The setUp() method is used in a YUITest.TestCase to set up data that may be needed for tests to be completed. This method is called immediately before each test is executed. For this example, setUp() creates a data object. The tearDown() is responsible for undoing what was done in setUp(). It is run immediately after each test is run and, in this case, deletes the data object that was created by setUp. These methods are optional.

The last section contains the actual tests to be run. Test method names must always begin with the word "test" (all lowercase) in order to differentiate them from other methods that may be added to the object.

The first test in this object is testName(), which runs various assertions on data.name. Inside of this method, a shortcut to YUITest.Assert is set up and used to run three assertions: isObject() on data, isString() on data.name and areEqual() to compare data.name to the expected value, "test". These assertions are arranged in order from least-specific to most-specific, which is the recommended way to arrange your assertions. Basically, the third assertion is useless to run unless the second has passes and the second can't possibly pass unless the first passed. Both isObject() and isString() accept a single argument, which is the value to test (you could optionally include a failure message as a second argument, though this is not required). The areEqual() method expects two arguments, the first being the expected value ("test") and the second being the actual value (data.name).

The second and third tests follow the same pattern as the first with the exception that they work with different data types. The testYear() method works with data.year, which is a number and so runs tests specifically for numbers (areEqual() can be used with all data types). The testBeta() method works with data.beta, which is a Boolean, and so it uses the isTrue() assertion instead of areEqual() (though it could also use areEqual(true, this.data.beta)). The last test uses a friendly name, which is just a method name with a space in it, and is set to fail. This is done to show the treatment and data available when a test fails.

Creating the second TestCase

Although it's possible that you'll have only one YUITest.TestCase object, typically there is more than one, and so this example includes a second YUITest.TestCase. This one tests some of the built-in functions of the Array object:

var arrayTestCase = new YUITest.TestCase({

    //name of the test case - if not provided, one is auto-generated
    name : "Array Tests",

    //---------------------------------------------------------------------
    // setUp and tearDown methods - optional
    //---------------------------------------------------------------------

    /*
     * Sets up data that is needed by each test.
     */
    setUp : function () {
        this.data = [0,1,2,3,4]
    },

    /*
     * Cleans up everything that was created by setUp().
     */
    tearDown : function () {
        delete this.data;
    },

    //---------------------------------------------------------------------
    // Test methods - names must begin with "test"
    //---------------------------------------------------------------------

    testPop : function () {
        var Assert = YUITest.Assert;

        var value = this.data.pop();

        Assert.areEqual(4, this.data.length);
        Assert.areEqual(4, value);
    },

    testPush : function () {
        var Assert = YUITest.Assert;

        this.data.push(5);

        Assert.areEqual(6, this.data.length);
        Assert.areEqual(5, this.data[5]);
    },

    testSplice : function () {
        var Assert = YUITest.Assert;

        this.data.splice(2, 1, 6, 7);

        Assert.areEqual(6, this.data.length);
        Assert.areEqual(6, this.data[2]);
        Assert.areEqual(7, this.data[3]);
    }

});

As with the first YUITest.TestCase, this one is split up into three sections: the name, the setUp() and tearDown() methods, and the test methods. The setUp() method in this YUITest.TestCase creates an array of data to be used by the various tests while the tearDown() method destroys the array. The test methods are very simple, testing the pop(), push(), and splice() methods. Each test method uses areEqual() exclusively, to show the different ways that it can be used. The testPop() method calls pop() on the array of values, then verifies that the length of the array has changed and that the value popped off is 4; the testPush() pushes a new value (5) onto the array and then verifies that the length of the array has changed and that the value is included in the correct location; the testSplice() method tests splice() by removing one value that's already in the array and inserting two in its place.

Creating the TestSuite

To better organize the two YUITest.TestCase objects, a YUITest.TestSuite is created and those two YUITest.TestCase objects are added to it:

var suite = new YUITest.TestSuite("Example Suite");
suite.add(dataTestCase);
suite.add(arrayTestCase);

The first line creates a new YUITest.TestSuite object using its constructor, which accepts a single argument - the name of the suite. As with the name of a YUITest.TestCase, the YUITest.TestSuite name is used to determine where execution is when tests are being executed. Although not required (one is generated if it's not provided), it is recommended that you select a meaningful name to aid in debugging.

Any number of YUITest.TestCase and YUITest.TestSuite objects can be added to a YUITest.TestSuite by using the add() method. In this example, the two YUITest.TestCase objects created earlier are added to the YUITest.TestSuite.

Setting up logging

Since the standalone YUI Test library isn't a graphical library, you'll need to use something to visualize the results. This example uses a YUI 3 Console object. To output the appropriate information into the console, a function is created to handle TestRunner events:

//function to handle events generated by the testrunner
var TestRunner = YUITest.TestRunner;

function logEvent(event){
    
    //data variables
    var message = "",
        messageType = "";
    
    switch(event.type){
        case TestRunner.BEGIN_EVENT:
            message = "Testing began at " + (new Date()).toString() + ".";
            messageType = "info";
            break;
            
        case TestRunner.COMPLETE_EVENT:
            message = Y.substitute("Testing completed at " +
                (new Date()).toString() + ".\n" +
                "Passed:{passed} Failed:{failed} " +
                "Total:{total} ({ignored} ignored)",
                event.results);
            messageType = "info";
            break;
            
        case TestRunner.TEST_FAIL_EVENT:
            message = event.testName + ": failed.\n" + event.error.getMessage();
            messageType = "fail";
            break;
            
        case TestRunner.TEST_IGNORE_EVENT:
            message = event.testName + ": ignored.";
            messageType = "ignore";
            break;
            
        case TestRunner.TEST_PASS_EVENT:
            message = event.testName + ": passed.";
            messageType = "pass";
            break;
            
        case TestRunner.TEST_SUITE_BEGIN_EVENT:
            message = "Test suite \"" + event.testSuite.name + "\" started.";
            messageType = "info";
            break;
            
        case TestRunner.TEST_SUITE_COMPLETE_EVENT:
            message = Y.substitute("Test suite \"" +
                event.testSuite.name + "\" completed" + ".\n" +
                "Passed:{passed} Failed:{failed} " +
                "Total:{total} ({ignored} ignored)",
                event.results);
            messageType = "info";
            break;
            
        case TestRunner.TEST_CASE_BEGIN_EVENT:
            message = "Test case \"" + event.testCase.name + "\" started.";
            messageType = "info";
            break;
            
        case TestRunner.TEST_CASE_COMPLETE_EVENT:
            message = Y.substitute("Test case \"" +
                event.testCase.name + "\" completed.\n" +
                "Passed:{passed} Failed:{failed} " +
                "Total:{total} ({ignored} ignored)",
                event.results);
            messageType = "info";
            break;
        default:
            message = "Unexpected event " + event.type;
            message = "info";
    }

    //only log if required
    Y.log(message, messageType, "TestRunner");
}

//listen for events to publish to the logger
TestRunner.attach(TestRunner.BEGIN_EVENT, logEvent);
TestRunner.attach(TestRunner.COMPLETE_EVENT, logEvent);
TestRunner.attach(TestRunner.TEST_CASE_BEGIN_EVENT, logEvent);
TestRunner.attach(TestRunner.TEST_CASE_COMPLETE_EVENT, logEvent);
TestRunner.attach(TestRunner.TEST_SUITE_BEGIN_EVENT, logEvent);
TestRunner.attach(TestRunner.TEST_SUITE_COMPLETE_EVENT, logEvent);
TestRunner.attach(TestRunner.TEST_PASS_EVENT, logEvent);
TestRunner.attach(TestRunner.TEST_FAIL_EVENT, logEvent);
TestRunner.attach(TestRunner.TEST_IGNORE_EVENT, logEvent);

The logEvent() function is used to handle all events. Based on the event, the message and the message type are determined and then a message is logged. This event handler is assigned to the TestRunner events by using the attach() method.

Running the tests

With all of the tests defined, the last step is to run them. This initialization is assigned to take place when all of the YUI components have been loaded:

//create the console
var r = new Y.Console({
    verbose : true,
    newestOnTop : false
});

r.render('#testLogger');

TestRunner.add(suite);

//run the tests
TestRunner.run();

Before running the tests, it's necessary to create a Y.Console object to display the results (otherwise the tests would run but you wouldn't see the results). After that, the YUITest.TestRunner is loaded with the YUITest.TestSuite object by calling add() (any number of YUITest.TestCase and YUITest.TestSuite objects can be added to a YUITest.TestRunner, this example only adds one for simplicity). The very last step is to call run(), which begins executing the tests in its queue and displays the results in the Y.Console.