In this recipe, we will learn how to handle the function's exceptional behavior, how to define custom exceptions, and how to test exceptions are really thrown for invalid input and these are exceptions of the intended types.
We've learnt already how to test whether or not a function performs the intended business logic. But what about its exceptional behavior? Let's say we supply invalid input parameters. We expect the function to warn us about them. The best practice would be to check the parameters have been passed onto the function entry point and thrown exceptions if any of the parameters are invalid. We need a way to test this logic as well.
Do you remember the utils.trim
function example? We have to modify it now for checking the validity of the input parameters:
var trim = function( str, charlist ) { if ( typeof str !== "string" ) { throw new this.InvalidTypeException("str argument must be a string"); } if ( charlist && typeof charlist !== "string" ) { throw new this.InvalidTypeException("charlist argument must be a string"); } if ( !str.length ) { throw new this.InvalidReferenceException("str argument must be empty"); } charlist = charlist || " \t\n\r\0\x0B"; return str.replace( new RegExp( "^[" + charlist + "]+|[" + charlist + "]+$", "g" ), '' ); }
Well, the function throws exception whenever it detects an invalid input but these are custom exceptions that must be defined. This can be done by declaring new error object constructors that inherit from standard JavaScript errors. Please find the implementation for this in the following example:
"strict mode"; var utils = (function( global ) { "use strict"; return { /** * Port of PHP trim function * @param {string} str * @param {string} charlist * @return {string} */ trim: function( str, charlist ) { // function body from the example above }, /** * @constructor */ InvalidReferenceException: function( message ) { this.name = "InvalidReferenceException"; this.message = message || "InvalidReferenceException thrown"; }, /** * @constructor */ InvalidTypeException: function( message ) { this.name = "InvalidTypeException"; this.message = message || "InvalidTypeException thrown"; } }; }()); // Inherit from ReferenceError utils.InvalidReferenceException.prototype = new ReferenceError(); utils.InvalidReferenceException.prototype.constructor = utils.InvalidReferenceException; // Inherit from TypeError utils.InvalidTypeException.prototype = new TypeError(); utils.InvalidTypeException.prototype.constructor = utils.InvalidTypeException;
Define the test scope and the assert expected exception is thrown. In this case, QUnit provides the
throws
method, which can be described with the following code:QUnit.test( "Test title", function( assert ) { assert.throws( "callback throwing exception, expected exception", "assertion title" ); });
Test whether
utils.trim
validates the input parameters and throws the intended exceptions:QUnit.test( "Test utils.trim contract violation", function( assert ){ assert.throws( function() { utils.trim(""); }, utils.InvalidReferenceException, "str agrument must not be empty" ); assert.throws( function() { utils.trim( 1 ); }, utils.InvalidTypeException, "str agrument must be a string" ); assert.throws( function() { utils.trim( "string", 1 ); }, utils.InvalidTypeException, "charlist agrument must be a string" ); });
Load the test runner in a browser and examine the results shown in the following screenshot: